Merge pull request #76821 from m4gr3d/prototype_godot_service_main

Refactor Godot Android architecture
This commit is contained in:
Yuri Sizov 2023-07-17 21:11:21 +02:00 committed by GitHub
commit 57919beb05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1701 additions and 1477 deletions

View File

@ -30,7 +30,7 @@
package com.godot.game;
import org.godotengine.godot.FullScreenGodotApp;
import org.godotengine.godot.GodotActivity;
import android.os.Bundle;
@ -38,7 +38,7 @@ import android.os.Bundle;
* Template activity for Godot Android builds.
* Feel free to extend and modify this class for your custom logic.
*/
public class GodotApp extends FullScreenGodotApp {
public class GodotApp extends GodotActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(R.style.GodotAppMainTheme);

View File

@ -39,7 +39,7 @@ import android.os.*
import android.util.Log
import android.widget.Toast
import androidx.window.layout.WindowMetricsCalculator
import org.godotengine.godot.FullScreenGodotApp
import org.godotengine.godot.GodotActivity
import org.godotengine.godot.GodotLib
import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.ProcessPhoenix
@ -55,7 +55,7 @@ import kotlin.math.min
*
* It also plays the role of the primary editor window.
*/
open class GodotEditor : FullScreenGodotApp() {
open class GodotEditor : GodotActivity() {
companion object {
private val TAG = GodotEditor::class.java.simpleName
@ -115,7 +115,7 @@ open class GodotEditor : FullScreenGodotApp() {
runOnUiThread {
// Enable long press, panning and scaling gestures
godotFragment?.renderView?.inputHandler?.apply {
godotFragment?.godot?.renderView?.inputHandler?.apply {
enableLongPress(longPressEnabled)
enablePanningAndScalingGestures(panScaleEnabled)
}
@ -318,7 +318,7 @@ open class GodotEditor : FullScreenGodotApp() {
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)

View File

@ -30,156 +30,10 @@
package org.godotengine.godot;
import org.godotengine.godot.utils.ProcessPhoenix;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
/**
* Base activity for Android apps intending to use Godot as the primary and only screen.
* Base abstract activity for Android apps intending to use Godot as the primary screen.
*
* It's also a reference implementation for how to setup and use the {@link Godot} fragment
* within an Android app.
* @deprecated Use {@link GodotActivity}
*/
public abstract class FullScreenGodotApp extends FragmentActivity implements GodotHost {
private static final String TAG = FullScreenGodotApp.class.getSimpleName();
protected static final String EXTRA_FORCE_QUIT = "force_quit_requested";
protected static final String EXTRA_NEW_LAUNCH = "new_launch_requested";
@Nullable
private Godot godotFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.godot_app_layout);
handleStartIntent(getIntent(), true);
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.godot_fragment_container);
if (currentFragment instanceof Godot) {
Log.v(TAG, "Reusing existing Godot fragment instance.");
godotFragment = (Godot)currentFragment;
} else {
Log.v(TAG, "Creating new Godot fragment instance.");
godotFragment = initGodotInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss();
}
}
@Override
public void onDestroy() {
Log.v(TAG, "Destroying Godot app...");
super.onDestroy();
terminateGodotInstance(godotFragment);
}
@Override
public final void onGodotForceQuit(Godot instance) {
runOnUiThread(() -> {
terminateGodotInstance(instance);
});
}
private void terminateGodotInstance(Godot instance) {
if (instance == godotFragment) {
Log.v(TAG, "Force quitting Godot instance");
ProcessPhoenix.forceQuit(FullScreenGodotApp.this);
}
}
@Override
public final void onGodotRestartRequested(Godot instance) {
runOnUiThread(() -> {
if (instance == godotFragment) {
// It's very hard to properly de-initialize Godot on Android to restart the game
// from scratch. Therefore, we need to kill the whole app process and relaunch it.
//
// Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
// releasing and reloading native libs or resetting their state somehow and clearing static data).
Log.v(TAG, "Restarting Godot instance...");
ProcessPhoenix.triggerRebirth(FullScreenGodotApp.this);
}
});
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
handleStartIntent(intent, false);
if (godotFragment != null) {
godotFragment.onNewIntent(intent);
}
}
private void handleStartIntent(Intent intent, boolean newLaunch) {
boolean forceQuitRequested = intent.getBooleanExtra(EXTRA_FORCE_QUIT, false);
if (forceQuitRequested) {
Log.d(TAG, "Force quit requested, terminating..");
ProcessPhoenix.forceQuit(this);
return;
}
if (!newLaunch) {
boolean newLaunchRequested = intent.getBooleanExtra(EXTRA_NEW_LAUNCH, false);
if (newLaunchRequested) {
Log.d(TAG, "New launch requested, restarting..");
Intent restartIntent = new Intent(intent).putExtra(EXTRA_NEW_LAUNCH, false);
ProcessPhoenix.triggerRebirth(this, restartIntent);
return;
}
}
}
@CallSuper
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (godotFragment != null) {
godotFragment.onActivityResult(requestCode, resultCode, data);
}
}
@CallSuper
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (godotFragment != null) {
godotFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
@Override
public void onBackPressed() {
if (godotFragment != null) {
godotFragment.onBackPressed();
} else {
super.onBackPressed();
}
}
/**
* Used to initialize the Godot fragment instance in {@link FullScreenGodotApp#onCreate(Bundle)}.
*/
@NonNull
protected Godot initGodotInstance() {
return new Godot();
}
@Nullable
protected final Godot getGodotFragment() {
return godotFragment;
}
}
@Deprecated
public abstract class FullScreenGodotApp extends GodotActivity {}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,965 @@
/**************************************************************************/
/* Godot.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
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog
import android.content.*
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Rect
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.*
import android.util.Log
import android.view.*
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.FrameLayout
import androidx.annotation.Keep
import androidx.annotation.StringRes
import com.google.android.vending.expansion.downloader.*
import org.godotengine.godot.input.GodotEditText
import org.godotengine.godot.io.directory.DirectoryAccessHandler
import org.godotengine.godot.io.file.FileAccessHandler
import org.godotengine.godot.plugin.GodotPluginRegistry
import org.godotengine.godot.tts.GodotTTS
import org.godotengine.godot.utils.GodotNetUtils
import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.PermissionsUtil.requestPermission
import org.godotengine.godot.utils.beginBenchmarkMeasure
import org.godotengine.godot.utils.benchmarkFile
import org.godotengine.godot.utils.dumpBenchmark
import org.godotengine.godot.utils.endBenchmarkMeasure
import org.godotengine.godot.utils.useBenchmark
import org.godotengine.godot.xr.XRMode
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.util.*
/**
* Core component used to interface with the native layer of the engine.
*
* 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) : SensorEventListener {
private companion object {
private val TAG = Godot::class.java.simpleName
}
private val pluginRegistry: GodotPluginRegistry by lazy {
GodotPluginRegistry.initializePluginRegistry(this)
}
private val mSensorManager: SensorManager by lazy {
requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager
}
private val mAccelerometer: Sensor by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
}
private val mGravity: Sensor by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
}
private val mMagnetometer: Sensor by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
}
private val mGyroscope: Sensor by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
}
private val mClipboard: ClipboardManager by lazy {
requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
}
private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int ->
if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
val decorView = requireActivity().window.decorView
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
}}
val tts = GodotTTS(context)
val directoryAccessHandler = DirectoryAccessHandler(context)
val fileAccessHandler = FileAccessHandler(context)
val netUtils = GodotNetUtils(context)
/**
* Tracks whether [onCreate] was completed successfully.
*/
private var initializationStarted = false
/**
* Tracks whether [GodotLib.initialize] was completed successfully.
*/
private var nativeLayerInitializeCompleted = false
/**
* Tracks whether [GodotLib.setup] was completed successfully.
*/
private var nativeLayerSetupCompleted = false
/**
* Tracks whether [onInitRenderView] was completed successfully.
*/
private var renderViewInitialized = false
private var primaryHost: GodotHost? = null
var io: GodotIO? = null
private var commandLine : MutableList<String> = ArrayList<String>()
private var xrMode = XRMode.REGULAR
private var expansionPackPath: String = ""
private var useApkExpansion = false
private var useImmersive = false
private var useDebugOpengl = false
private var containerLayout: FrameLayout? = null
var renderView: GodotRenderView? = null
/**
* Returns true if the native engine has been initialized through [onInitNativeLayer], false otherwise.
*/
private fun isNativeInitialized() = nativeLayerInitializeCompleted && nativeLayerSetupCompleted
/**
* Returns true if the engine has been initialized, false otherwise.
*/
fun isInitialized() = initializationStarted && 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.
*
* @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
}
beginBenchmarkMeasure("Godot::onCreate")
try {
this.primaryHost = primaryHost
val activity = requireActivity()
val window = activity.window
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
GodotPluginRegistry.initializePluginRegistry(this)
if (io == null) {
io = GodotIO(activity)
}
// check for apk expansion API
commandLine = getCommandLine()
var mainPackMd5: String? = null
var mainPackKey: String? = null
val newArgs: MutableList<String> = ArrayList()
var i = 0
while (i < commandLine.size) {
val hasExtra: Boolean = i < commandLine.size - 1
if (commandLine[i] == XRMode.REGULAR.cmdLineArg) {
xrMode = XRMode.REGULAR
} else if (commandLine[i] == XRMode.OPENXR.cmdLineArg) {
xrMode = XRMode.OPENXR
} else if (commandLine[i] == "--debug_opengl") {
useDebugOpengl = true
} else if (commandLine[i] == "--use_immersive") {
useImmersive = true
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar
View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
registerUiChangeListener()
} else if (commandLine[i] == "--use_apk_expansion") {
useApkExpansion = true
} else if (hasExtra && commandLine[i] == "--apk_expansion_md5") {
mainPackMd5 = commandLine[i + 1]
i++
} else if (hasExtra && commandLine[i] == "--apk_expansion_key") {
mainPackKey = commandLine[i + 1]
val prefs = activity.getSharedPreferences(
"app_data_keys",
Context.MODE_PRIVATE
)
val editor = prefs.edit()
editor.putString("store_public_key", mainPackKey)
editor.apply()
i++
} else if (commandLine[i] == "--benchmark") {
useBenchmark = true
newArgs.add(commandLine[i])
} else if (hasExtra && commandLine[i] == "--benchmark-file") {
useBenchmark = true
newArgs.add(commandLine[i])
// Retrieve the filepath
benchmarkFile = commandLine[i + 1]
newArgs.add(commandLine[i + 1])
i++
} else if (commandLine[i].trim().isNotEmpty()) {
newArgs.add(commandLine[i])
}
i++
}
if (newArgs.isEmpty()) {
commandLine = mutableListOf()
} else {
commandLine = 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,
0
).versionCode + "." + activity.packageName + ".obb"
} catch (e: java.lang.Exception) {
Log.e(TAG, "Unable to build full path to the app's expansion files", e)
}
val f = File(expansionPackPath)
var packValid = true
if (!f.exists()) {
packValid = false
} else if (obbIsCorrupted(expansionPackPath, mainPackMd5)) {
packValid = false
try {
f.delete()
} catch (_: java.lang.Exception) {
}
}
if (!packValid) {
// Aborting engine initialization
throw IllegalArgumentException("Invalid expansion pack")
}
}
initializationStarted = true
} catch (e: java.lang.Exception) {
// Clear the primary host and rethrow
this.primaryHost = null
initializationStarted = false
throw e
} finally {
endBenchmarkMeasure("Godot::onCreate");
}
}
/**
* 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
}
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,
)
}
if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) {
nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts)
if (!nativeLayerSetupCompleted) {
Log.e(TAG, "Unable to setup the Godot engine! Aborting...")
alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit)
}
}
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.
*
* @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
*/
@JvmOverloads
fun onInitRenderView(host: GodotHost, providedContainerLayout: FrameLayout = FrameLayout(host.activity)): FrameLayout? {
if (!isNativeInitialized()) {
throw IllegalStateException("onInitNativeLayer() must be invoked successfully prior to initializing the render view")
}
try {
val activity: Activity = host.activity
containerLayout = providedContainerLayout
containerLayout?.removeAllViews()
containerLayout?.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
// GodotEditText layout
val editText = GodotEditText(activity)
editText.layoutParams =
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
activity.resources.getDimension(R.dimen.text_edit_height).toInt()
)
// ...add to FrameLayout
containerLayout?.addView(editText)
renderView = if (usesVulkan()) {
if (!meetsVulkanRequirements(activity.packageManager)) {
alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit)
return null
}
GodotVulkanRenderView(host, this)
} else {
// Fallback to openGl
GodotGLRenderView(host, this, xrMode, useDebugOpengl)
}
if (host == primaryHost) {
renderView!!.startRenderer()
}
val view: View = renderView!!.view
containerLayout?.addView(
view,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
)
editText.setView(renderView)
io?.setEdit(editText)
// Listeners for keyboard height.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Report the height of virtual keyboard as it changes during the animation.
val decorView = activity.window.decorView
decorView.setWindowInsetsAnimationCallback(object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
var startBottom = 0
var endBottom = 0
override fun onPrepare(animation: WindowInsetsAnimation) {
startBottom = decorView.rootWindowInsets.getInsets(WindowInsets.Type.ime()).bottom
}
override fun onStart(animation: WindowInsetsAnimation, bounds: WindowInsetsAnimation.Bounds): WindowInsetsAnimation.Bounds {
endBottom = decorView.rootWindowInsets.getInsets(WindowInsets.Type.ime()).bottom
return bounds
}
override fun onProgress(windowInsets: WindowInsets, list: List<WindowInsetsAnimation>): WindowInsets {
// Find the IME animation.
var imeAnimation: WindowInsetsAnimation? = null
for (animation in list) {
if (animation.typeMask and WindowInsets.Type.ime() != 0) {
imeAnimation = animation
break
}
}
// Update keyboard height based on IME animation.
if (imeAnimation != null) {
val interpolatedFraction = imeAnimation.interpolatedFraction
// Linear interpolation between start and end values.
val keyboardHeight = startBottom * (1.0f - interpolatedFraction) + endBottom * interpolatedFraction
GodotLib.setVirtualKeyboardHeight(keyboardHeight.toInt())
}
return windowInsets
}
override fun onEnd(animation: WindowInsetsAnimation) {}
})
} else {
// Infer the virtual keyboard height using visible area.
view.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
// Don't allocate a new Rect every time the callback is called.
val visibleSize = Rect()
override fun onGlobalLayout() {
val surfaceView = renderView!!.view
surfaceView.getWindowVisibleDisplayFrame(visibleSize)
val keyboardHeight = surfaceView.height - visibleSize.bottom
GodotLib.setVirtualKeyboardHeight(keyboardHeight)
}
})
}
if (host == primaryHost) {
renderView!!.queueOnRenderThread {
for (plugin in pluginRegistry.allPlugins) {
plugin.onRegisterPluginWithGodotNative()
}
setKeepScreenOn(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on")))
}
// Include the returned non-null views in the Godot view hierarchy.
for (plugin in pluginRegistry.allPlugins) {
val pluginView = plugin.onMainCreate(activity)
if (pluginView != null) {
if (plugin.shouldBeOnTop()) {
containerLayout?.addView(pluginView)
} else {
containerLayout?.addView(pluginView, 0)
}
}
}
}
renderViewInitialized = true
} finally {
if (!renderViewInitialized) {
containerLayout?.removeAllViews()
containerLayout = null
}
}
return containerLayout
}
fun onResume(host: GodotHost) {
if (host != primaryHost) {
return
}
renderView!!.onActivityResumed()
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
mSensorManager.registerListener(this, mGravity, SensorManager.SENSOR_DELAY_GAME)
mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME)
mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME)
if (useImmersive) {
val window = requireActivity().window
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar
View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
}
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainResume()
}
}
fun onPause(host: GodotHost) {
if (host != primaryHost) {
return
}
renderView!!.onActivityPaused()
mSensorManager.unregisterListener(this)
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainPause()
}
}
fun onDestroy(primaryHost: GodotHost) {
if (this.primaryHost != primaryHost) {
return
}
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainDestroy()
}
GodotLib.ondestroy()
forceQuit()
}
/**
* Activity result callback
*/
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainActivityResult(requestCode, resultCode, data)
}
}
/**
* Permissions request callback
*/
fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>,
grantResults: IntArray
) {
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults)
}
for (i in permissions.indices) {
GodotLib.requestPermissionResult(
permissions[i],
grantResults[i] == PackageManager.PERMISSION_GRANTED
)
}
}
/**
* Invoked on the render thread when the Godot setup is complete.
*/
private fun onGodotSetupCompleted() {
for (plugin in pluginRegistry.allPlugins) {
plugin.onGodotSetupCompleted()
}
primaryHost?.onGodotSetupCompleted()
}
/**
* Invoked on the render thread when the Godot main loop has started.
*/
private fun onGodotMainLoopStarted() {
for (plugin in pluginRegistry.allPlugins) {
plugin.onGodotMainLoopStarted()
}
primaryHost?.onGodotMainLoopStarted()
}
private fun restart() {
primaryHost?.onGodotRestartRequested(this)
}
private fun registerUiChangeListener() {
val decorView = requireActivity().window.decorView
decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener)
}
@Keep
private fun alert(message: String, title: String) {
alert(message, title, null)
}
private fun alert(
@StringRes messageResId: Int,
@StringRes titleResId: Int,
okCallback: Runnable?
) {
val res: Resources = getActivity()?.resources ?: return
alert(res.getString(messageResId), res.getString(titleResId), okCallback)
}
private fun alert(message: String, title: String, okCallback: Runnable?) {
val activity: Activity = getActivity() ?: return
runOnUiThread(Runnable {
val builder = AlertDialog.Builder(activity)
builder.setMessage(message).setTitle(title)
builder.setPositiveButton(
"OK"
) { dialog: DialogInterface, id: Int ->
okCallback?.run()
dialog.cancel()
}
val dialog = builder.create()
dialog.show()
})
}
/**
* Queue a runnable to be run on the render thread.
*
* This must be called after the render thread has started.
*/
fun runOnRenderThread(action: Runnable) {
if (renderView != null) {
renderView!!.queueOnRenderThread(action)
}
}
/**
* 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.
*/
fun runOnUiThread(action: Runnable) {
val activity: Activity = getActivity() ?: return
activity.runOnUiThread(action)
}
/**
* Returns true if the call is being made on the Ui thread.
*/
private fun isOnUiThread() = Looper.myLooper() == Looper.getMainLooper()
/**
* Returns true if `Vulkan` is used for rendering.
*/
private fun usesVulkan(): Boolean {
val renderer = GodotLib.getGlobal("rendering/renderer/rendering_method")
val renderingDevice = GodotLib.getGlobal("rendering/rendering_device/driver")
return ("forward_plus" == renderer || "mobile" == renderer) && "vulkan" == renderingDevice
}
/**
* Returns true if the device meets the base requirements for Vulkan support, false otherwise.
*/
private fun meetsVulkanRequirements(packageManager: PackageManager?): Boolean {
if (packageManager == null) {
return false
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL, 1)) {
// Optional requirements.. log as warning if missing
Log.w(TAG, "The vulkan hardware level does not meet the minimum requirement: 1")
}
// Check for api version 1.0
return packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, 0x400003)
}
return false
}
private fun setKeepScreenOn(p_enabled: Boolean) {
runOnUiThread {
if (p_enabled) {
getActivity()?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
getActivity()?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
}
fun hasClipboard(): Boolean {
return mClipboard.hasPrimaryClip()
}
fun getClipboard(): String? {
val clipData = mClipboard.primaryClip ?: return ""
val text = clipData.getItemAt(0).text ?: return ""
return text.toString()
}
fun setClipboard(text: String?) {
val clip = ClipData.newPlainText("myLabel", text)
mClipboard.setPrimaryClip(clip)
}
private fun forceQuit() {
forceQuit(0)
}
@Keep
private fun forceQuit(instanceId: Int): Boolean {
if (primaryHost == null) {
return false
}
return if (instanceId == 0) {
primaryHost!!.onGodotForceQuit(this)
true
} else {
primaryHost!!.onGodotForceQuit(instanceId)
}
}
fun onBackPressed(host: GodotHost) {
if (host != primaryHost) {
return
}
var shouldQuit = true
for (plugin in pluginRegistry.allPlugins) {
if (plugin.onMainBackPressed()) {
shouldQuit = false
}
}
if (shouldQuit && renderView != null) {
renderView!!.queueOnRenderThread { GodotLib.back() }
}
}
private fun getRotatedValues(values: FloatArray?): FloatArray? {
if (values == null || values.size != 3) {
return values
}
val display =
(requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
val displayRotation = display.rotation
val rotatedValues = FloatArray(3)
when (displayRotation) {
Surface.ROTATION_0 -> {
rotatedValues[0] = values[0]
rotatedValues[1] = values[1]
rotatedValues[2] = values[2]
}
Surface.ROTATION_90 -> {
rotatedValues[0] = -values[1]
rotatedValues[1] = values[0]
rotatedValues[2] = values[2]
}
Surface.ROTATION_180 -> {
rotatedValues[0] = -values[0]
rotatedValues[1] = -values[1]
rotatedValues[2] = values[2]
}
Surface.ROTATION_270 -> {
rotatedValues[0] = values[1]
rotatedValues[1] = -values[0]
rotatedValues[2] = values[2]
}
}
return rotatedValues
}
override fun onSensorChanged(event: SensorEvent) {
if (renderView == null) {
return
}
when (event.sensor.type) {
Sensor.TYPE_ACCELEROMETER -> {
val rotatedValues = getRotatedValues(event.values)
renderView!!.queueOnRenderThread {
GodotLib.accelerometer(
-rotatedValues!![0], -rotatedValues[1], -rotatedValues[2]
)
}
}
Sensor.TYPE_GRAVITY -> {
val rotatedValues = getRotatedValues(event.values)
renderView!!.queueOnRenderThread {
GodotLib.gravity(
-rotatedValues!![0], -rotatedValues[1], -rotatedValues[2]
)
}
}
Sensor.TYPE_MAGNETIC_FIELD -> {
val rotatedValues = getRotatedValues(event.values)
renderView!!.queueOnRenderThread {
GodotLib.magnetometer(
-rotatedValues!![0], -rotatedValues[1], -rotatedValues[2]
)
}
}
Sensor.TYPE_GYROSCOPE -> {
val rotatedValues = getRotatedValues(event.values)
renderView!!.queueOnRenderThread {
GodotLib.gyroscope(
rotatedValues!![0], rotatedValues[1], rotatedValues[2]
)
}
}
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Do something here if sensor accuracy changes.
}
/**
* Used by the native code (java_godot_wrapper.h) to vibrate the device.
* @param durationMs
*/
@SuppressLint("MissingPermission")
@Keep
private fun vibrate(durationMs: Int) {
if (durationMs > 0 && requestPermission("VIBRATE")) {
val vibratorService = getActivity()?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibratorService.vibrate(
VibrationEffect.createOneShot(
durationMs.toLong(),
VibrationEffect.DEFAULT_AMPLITUDE
)
)
} else {
// deprecated in API 26
vibratorService.vibrate(durationMs.toLong())
}
}
}
private fun getCommandLine(): MutableList<String> {
val original: MutableList<String> = parseCommandLine()
val hostCommandLine = primaryHost?.commandLine
if (hostCommandLine != null && hostCommandLine.isNotEmpty()) {
original.addAll(hostCommandLine)
}
return original
}
private fun parseCommandLine(): MutableList<String> {
val inputStream: InputStream
return try {
inputStream = requireActivity().assets.open("_cl_")
val len = ByteArray(4)
var r = inputStream.read(len)
if (r < 4) {
return mutableListOf()
}
val argc =
(len[3].toInt() and 0xFF) shl 24 or ((len[2].toInt() and 0xFF) shl 16) or ((len[1].toInt() and 0xFF) shl 8) or (len[0].toInt() and 0xFF)
val cmdline = ArrayList<String>(argc)
for (i in 0 until argc) {
r = inputStream.read(len)
if (r < 4) {
return mutableListOf()
}
val strlen =
(len[3].toInt() and 0xFF) shl 24 or ((len[2].toInt() and 0xFF) shl 16) or ((len[1].toInt() and 0xFF) shl 8) or (len[0].toInt() and 0xFF)
if (strlen > 65535) {
return mutableListOf()
}
val arg = ByteArray(strlen)
r = inputStream.read(arg)
if (r == strlen) {
cmdline[i] = String(arg, StandardCharsets.UTF_8)
}
}
cmdline
} catch (e: Exception) {
// The _cl_ file can be missing with no adverse effect
mutableListOf()
}
}
/**
* 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.
*/
@Keep
private fun getInputFallbackMapping(): String? {
return xrMode.inputFallbackMapping
}
fun requestPermission(name: String?): Boolean {
return requestPermission(name, getActivity())
}
fun requestPermissions(): Boolean {
return PermissionsUtil.requestManifestPermissions(getActivity())
}
fun getGrantedPermissions(): Array<String?>? {
return PermissionsUtil.getGrantedPermissions(getActivity())
}
@Keep
private fun getCACertificates(): String {
return GodotNetUtils.getCACertificates()
}
private fun obbIsCorrupted(f: String, mainPackMd5: String): Boolean {
return try {
val fis: InputStream = FileInputStream(f)
// Create MD5 Hash
val buffer = ByteArray(16384)
val complete = MessageDigest.getInstance("MD5")
var numRead: Int
do {
numRead = fis.read(buffer)
if (numRead > 0) {
complete.update(buffer, 0, numRead)
}
} while (numRead != -1)
fis.close()
val messageDigest = complete.digest()
// Create Hex String
val hexString = StringBuilder()
for (b in messageDigest) {
var s = Integer.toHexString(0xFF and b.toInt())
if (s.length == 1) {
s = "0$s"
}
hexString.append(s)
}
val md5str = hexString.toString()
md5str != mainPackMd5
} catch (e: java.lang.Exception) {
e.printStackTrace()
true
}
}
@Keep
private fun initInputDevices() {
renderView!!.initInputDevices()
}
@Keep
private fun createNewGodotInstance(args: Array<String>): Int {
return primaryHost?.onNewGodotInstanceRequested(args) ?: 0
}
@Keep
private fun nativeBeginBenchmarkMeasure(label: String) {
beginBenchmarkMeasure(label)
}
@Keep
private fun nativeEndBenchmarkMeasure(label: String) {
endBenchmarkMeasure(label)
}
@Keep
private fun nativeDumpBenchmark(benchmarkFile: String) {
dumpBenchmark(fileAccessHandler, benchmarkFile)
}
}

View File

@ -0,0 +1,167 @@
/**************************************************************************/
/* GodotActivity.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
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.annotation.CallSuper
import androidx.fragment.app.FragmentActivity
import org.godotengine.godot.utils.ProcessPhoenix
/**
* Base abstract activity for Android apps intending to use Godot as the primary screen.
*
* Also a reference implementation for how to setup and use the [GodotFragment] fragment
* within an Android app.
*/
abstract class GodotActivity : FragmentActivity(), GodotHost {
companion object {
private val TAG = GodotActivity::class.java.simpleName
@JvmStatic
protected val EXTRA_FORCE_QUIT = "force_quit_requested"
@JvmStatic
protected val EXTRA_NEW_LAUNCH = "new_launch_requested"
}
/**
* Interaction with the [Godot] object is delegated to the [GodotFragment] class.
*/
protected var godotFragment: GodotFragment? = null
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.godot_app_layout)
handleStartIntent(intent, true)
val currentFragment = supportFragmentManager.findFragmentById(R.id.godot_fragment_container)
if (currentFragment is GodotFragment) {
Log.v(TAG, "Reusing existing Godot fragment instance.")
godotFragment = currentFragment
} else {
Log.v(TAG, "Creating new Godot fragment instance.")
godotFragment = initGodotInstance()
supportFragmentManager.beginTransaction().replace(R.id.godot_fragment_container, godotFragment!!).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss()
}
}
override fun onDestroy() {
Log.v(TAG, "Destroying Godot app...")
super.onDestroy()
if (godotFragment != null) {
terminateGodotInstance(godotFragment!!.godot)
}
}
override fun onGodotForceQuit(instance: Godot) {
runOnUiThread { terminateGodotInstance(instance) }
}
private fun terminateGodotInstance(instance: Godot) {
if (godotFragment != null && instance === godotFragment!!.godot) {
Log.v(TAG, "Force quitting Godot instance")
ProcessPhoenix.forceQuit(this)
}
}
override fun onGodotRestartRequested(instance: Godot) {
runOnUiThread {
if (godotFragment != null && instance === godotFragment!!.godot) {
// It's very hard to properly de-initialize Godot on Android to restart the game
// from scratch. Therefore, we need to kill the whole app process and relaunch it.
//
// Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
// releasing and reloading native libs or resetting their state somehow and clearing static data).
Log.v(TAG, "Restarting Godot instance...")
ProcessPhoenix.triggerRebirth(this)
}
}
}
override fun onNewIntent(newIntent: Intent) {
super.onNewIntent(newIntent)
intent = newIntent
handleStartIntent(newIntent, false)
godotFragment?.onNewIntent(newIntent)
}
private fun handleStartIntent(intent: Intent, newLaunch: Boolean) {
val forceQuitRequested = intent.getBooleanExtra(EXTRA_FORCE_QUIT, false)
if (forceQuitRequested) {
Log.d(TAG, "Force quit requested, terminating..")
ProcessPhoenix.forceQuit(this)
return
}
if (!newLaunch) {
val newLaunchRequested = intent.getBooleanExtra(EXTRA_NEW_LAUNCH, false)
if (newLaunchRequested) {
Log.d(TAG, "New launch requested, restarting..")
val restartIntent = Intent(intent).putExtra(EXTRA_NEW_LAUNCH, false)
ProcessPhoenix.triggerRebirth(this, restartIntent)
return
}
}
}
@CallSuper
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
godotFragment?.onActivityResult(requestCode, resultCode, data)
}
@CallSuper
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
godotFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onBackPressed() {
godotFragment?.onBackPressed() ?: super.onBackPressed()
}
override fun getActivity(): Activity? {
return this
}
/**
* Used to initialize the Godot fragment instance in [onCreate].
*/
protected open fun initGodotInstance(): GodotFragment {
return GodotFragment()
}
}

View File

@ -0,0 +1,429 @@
/**************************************************************************/
/* GodotFragment.java */
/**************************************************************************/
/* 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;
import org.godotengine.godot.utils.BenchmarkUtils;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Messenger;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;
import com.google.android.vending.expansion.downloader.IStub;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* Base fragment for Android apps intending to use Godot for part of the app's UI.
*/
public class GodotFragment extends Fragment implements IDownloaderClient, GodotHost {
private static final String TAG = GodotFragment.class.getSimpleName();
private IStub mDownloaderClientStub;
private TextView mStatusText;
private TextView mProgressFraction;
private TextView mProgressPercent;
private TextView mAverageSpeed;
private TextView mTimeRemaining;
private ProgressBar mPB;
private View mDashboard;
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;
mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState));
}
}
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;
public Godot getGodot() {
return godot;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (getParentFragment() instanceof GodotHost) {
parentHost = (GodotHost)getParentFragment();
} else if (getActivity() instanceof GodotHost) {
parentHost = (GodotHost)getActivity();
}
}
@Override
public void onDetach() {
super.onDetach();
parentHost = null;
}
@CallSuper
@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);
}
@CallSuper
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
godot.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onServiceConnected(Messenger m) {
IDownloaderService remoteService = DownloaderServiceMarshaller.CreateProxy(m);
remoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
@Override
public void onCreate(Bundle icicle) {
BenchmarkUtils.beginBenchmarkMeasure("GodotFragment::onCreate");
super.onCreate(icicle);
final Activity activity = getActivity();
mCurrentIntent = activity.getIntent();
godot = new Godot(requireContext());
performEngineInitialization();
BenchmarkUtils.endBenchmarkMeasure("GodotFragment::onCreate");
}
private void performEngineInitialization() {
try {
godot.onCreate(this);
if (!godot.onInitNativeLayer(this)) {
throw new IllegalStateException("Unable to initialize engine native layer");
}
godotContainerLayout = godot.onInitRenderView(this);
if (godotContainerLayout == null) {
throw new IllegalStateException("Unable to initialize engine render view");
}
} catch (IllegalArgumentException ignored) {
final Activity activity = getActivity();
Intent notifierIntent = new Intent(activity, activity.getClass());
notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
pendingIntent = PendingIntent.getActivity(activity, 0,
notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(activity, 0,
notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
int startResult;
try {
startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(getContext(), pendingIntent, GodotDownloaderService.class);
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
// This is where you do set up to display the download
// progress (next step in onCreateView)
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, GodotDownloaderService.class);
return;
}
// Restart engine initialization
performEngineInitialization();
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Unable to start download service", e);
}
}
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle icicle) {
if (mDownloaderClientStub != null) {
View downloadingExpansionView =
inflater.inflate(R.layout.downloading_expansion, container, false);
mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar);
mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText);
mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction);
mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage);
mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed);
mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining);
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;
}
return godotContainerLayout;
}
@Override
public void onDestroy() {
godot.onDestroy(this);
super.onDestroy();
}
@Override
public void onPause() {
super.onPause();
if (!godot.isInitialized()) {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.disconnect(getActivity());
}
return;
}
godot.onPause(this);
}
@Override
public void onResume() {
super.onResume();
if (!godot.isInitialized()) {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.connect(getActivity());
}
return;
}
godot.onResume(this);
}
public void onBackPressed() {
godot.onBackPressed(this);
}
/**
* The download state should trigger changes in the UI --- it may be useful
* to show the state as being indeterminate at times. This sample can be
* considered a guideline.
*/
@Override
public void onDownloadStateChanged(int newState) {
setState(newState);
boolean showDashboard = true;
boolean showCellMessage = false;
boolean paused;
boolean indeterminate;
switch (newState) {
case IDownloaderClient.STATE_IDLE:
// STATE_IDLE means the service is listening, so it's
// safe to start making remote service calls.
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_CONNECTING:
case IDownloaderClient.STATE_FETCHING_URL:
showDashboard = true;
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_DOWNLOADING:
paused = false;
showDashboard = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_FAILED_CANCELED:
case IDownloaderClient.STATE_FAILED:
case IDownloaderClient.STATE_FAILED_FETCHING_URL:
case IDownloaderClient.STATE_FAILED_UNLICENSED:
paused = true;
showDashboard = false;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
showDashboard = false;
paused = true;
indeterminate = false;
showCellMessage = true;
break;
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_ROAMING:
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_COMPLETED:
showDashboard = false;
paused = false;
indeterminate = false;
performEngineInitialization();
return;
default:
paused = true;
indeterminate = true;
showDashboard = true;
}
int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE;
if (mDashboard.getVisibility() != newDashboardVisibility) {
mDashboard.setVisibility(newDashboardVisibility);
}
int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE;
if (mCellMessage.getVisibility() != cellMessageVisibility) {
mCellMessage.setVisibility(cellMessageVisibility);
}
mPB.setIndeterminate(indeterminate);
setButtonPausedState(paused);
}
@Override
public void onDownloadProgress(DownloadProgressInfo progress) {
mAverageSpeed.setText(getString(R.string.kilobytes_per_second,
Helpers.getSpeedString(progress.mCurrentSpeed)));
mTimeRemaining.setText(getString(R.string.time_remaining,
Helpers.getTimeRemaining(progress.mTimeRemaining)));
mPB.setMax((int)(progress.mOverallTotal >> 8));
mPB.setProgress((int)(progress.mOverallProgress >> 8));
mProgressPercent.setText(String.format(Locale.ENGLISH, "%d %%", progress.mOverallProgress * 100 / progress.mOverallTotal));
mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress,
progress.mOverallTotal));
}
@CallSuper
@Override
public List<String> getCommandLine() {
return parentHost != null ? parentHost.getCommandLine() : Collections.emptyList();
}
@CallSuper
@Override
public void onGodotSetupCompleted() {
if (parentHost != null) {
parentHost.onGodotSetupCompleted();
}
}
@CallSuper
@Override
public void onGodotMainLoopStarted() {
if (parentHost != null) {
parentHost.onGodotMainLoopStarted();
}
}
@Override
public void onGodotForceQuit(Godot instance) {
if (parentHost != null) {
parentHost.onGodotForceQuit(instance);
}
}
@Override
public boolean onGodotForceQuit(int godotInstanceId) {
return parentHost != null && parentHost.onGodotForceQuit(godotInstanceId);
}
@Override
public void onGodotRestartRequested(Godot instance) {
if (parentHost != null) {
parentHost.onGodotRestartRequested(instance);
}
}
@Override
public int onNewGodotInstanceRequested(String[] args) {
if (parentHost != null) {
return parentHost.onNewGodotInstanceRequested(args);
}
return 0;
}
}

View File

@ -29,10 +29,10 @@
/**************************************************************************/
package org.godotengine.godot;
import org.godotengine.godot.gl.GLSurfaceView;
import org.godotengine.godot.gl.GodotRenderer;
import org.godotengine.godot.input.GodotInputHandler;
import org.godotengine.godot.utils.GLUtils;
import org.godotengine.godot.xr.XRMode;
import org.godotengine.godot.xr.ovr.OvrConfigChooser;
import org.godotengine.godot.xr.ovr.OvrContextFactory;
@ -78,22 +78,23 @@ import java.io.InputStream;
* bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
*/
public 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(Context context, Godot godot, XRMode xrMode, boolean p_use_debug_opengl) {
super(context);
GLUtils.use_debug_opengl = p_use_debug_opengl;
public GodotGLRenderView(GodotHost host, Godot godot, XRMode xrMode, boolean useDebugOpengl) {
super(host.getActivity());
this.host = host;
this.godot = godot;
this.inputHandler = new GodotInputHandler(this);
this.godotRenderer = new GodotRenderer();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
}
init(xrMode, false);
init(xrMode, false, useDebugOpengl);
}
@Override
@ -123,7 +124,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
@Override
public void onBackPressed() {
godot.onBackPressed();
godot.onBackPressed(host);
}
@Override
@ -233,7 +234,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
return super.onResolvePointerIcon(me, pointerIndex);
}
private void init(XRMode xrMode, boolean translucent) {
private void init(XRMode xrMode, boolean translucent, boolean useDebugOpengl) {
setPreserveEGLContextOnPause(true);
setFocusableInTouchMode(true);
switch (xrMode) {
@ -262,7 +263,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
/* Setup the context factory for 2.0 rendering.
* See ContextFactory class definition below
*/
setEGLContextFactory(new RegularContextFactory());
setEGLContextFactory(new RegularContextFactory(useDebugOpengl));
/* We need to choose an EGLConfig that matches the format of
* our surface exactly. This is going to be done in our
@ -275,7 +276,10 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView
new RegularConfigChooser(8, 8, 8, 8, 16, 0)));
break;
}
}
@Override
public void startRenderer() {
/* Set the renderer responsible for frame rendering */
setRenderer(godotRenderer);
}

View File

@ -30,11 +30,13 @@
package org.godotengine.godot;
import android.app.Activity;
import java.util.Collections;
import java.util.List;
/**
* Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} fragment.
* Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} engine.
*/
public interface GodotHost {
/**
@ -86,4 +88,9 @@ public interface GodotHost {
default int onNewGodotInstanceRequested(String[] args) {
return 0;
}
/**
* Provide access to the Activity hosting the Godot engine.
*/
Activity getActivity();
}

View File

@ -39,6 +39,11 @@ public interface GodotRenderView {
void initInputDevices();
/**
* Starts the thread that will drive Godot's rendering.
*/
void startRenderer();
void queueOnRenderThread(Runnable event);
void onActivityPaused();

View File

@ -0,0 +1,54 @@
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.
*/
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()
}

View File

@ -35,7 +35,6 @@ import org.godotengine.godot.vulkan.VkRenderer;
import org.godotengine.godot.vulkan.VkSurfaceView;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -52,14 +51,16 @@ import androidx.annotation.Keep;
import java.io.InputStream;
public 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(Context context, Godot godot) {
super(context);
public GodotVulkanRenderView(GodotHost host, Godot godot) {
super(host.getActivity());
this.host = host;
this.godot = godot;
mInputHandler = new GodotInputHandler(this);
mRenderer = new VkRenderer();
@ -67,6 +68,10 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
}
setFocusableInTouchMode(true);
}
@Override
public void startRenderer() {
startRenderer(mRenderer);
}
@ -97,7 +102,7 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV
@Override
public void onBackPressed() {
godot.onBackPressed();
godot.onBackPressed(host);
}
@Override

View File

@ -33,6 +33,7 @@ package org.godotengine.godot.tts;
import org.godotengine.godot.GodotLib;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
@ -62,7 +63,7 @@ public class GodotTTS extends UtteranceProgressListener {
final private static int EVENT_CANCEL = 2;
final private static int EVENT_BOUNDARY = 3;
final private Activity activity;
private final Context context;
private TextToSpeech synth;
private LinkedList<GodotUtterance> queue;
final private Object lock = new Object();
@ -71,8 +72,8 @@ public class GodotTTS extends UtteranceProgressListener {
private boolean speaking;
private boolean paused;
public GodotTTS(Activity p_activity) {
activity = p_activity;
public GodotTTS(Context context) {
this.context = context;
}
private void updateTTS() {
@ -188,7 +189,7 @@ public class GodotTTS extends UtteranceProgressListener {
* Initialize synth and query.
*/
public void init() {
synth = new TextToSpeech(activity, null);
synth = new TextToSpeech(context, null);
queue = new LinkedList<GodotUtterance>();
synth.setOnUtteranceProgressListener(this);

View File

@ -44,8 +44,6 @@ public class GLUtils {
public static final boolean DEBUG = false;
public static boolean use_debug_opengl = false;
private static final String[] ATTRIBUTES_NAMES = new String[] {
"EGL_BUFFER_SIZE",
"EGL_ALPHA_SIZE",

View File

@ -36,7 +36,8 @@ import android.net.wifi.WifiManager;
import android.util.Base64;
import android.util.Log;
import java.io.StringWriter;
import androidx.annotation.NonNull;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
@ -50,9 +51,9 @@ public class GodotNetUtils {
/* A single, reference counted, multicast lock, or null if permission CHANGE_WIFI_MULTICAST_STATE is missing */
private WifiManager.MulticastLock multicastLock;
public GodotNetUtils(Activity p_activity) {
if (PermissionsUtil.hasManifestPermission(p_activity, "android.permission.CHANGE_WIFI_MULTICAST_STATE")) {
WifiManager wifi = (WifiManager)p_activity.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
public GodotNetUtils(Context context) {
if (PermissionsUtil.hasManifestPermission(context, "android.permission.CHANGE_WIFI_MULTICAST_STATE")) {
WifiManager wifi = (WifiManager)context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
multicastLock = wifi.createMulticastLock("GodotMulticastLock");
multicastLock.setReferenceCounted(true);
}
@ -91,7 +92,7 @@ public class GodotNetUtils {
* @see https://developer.android.com/reference/java/security/KeyStore .
* @return A string of concatenated X509 certificates in PEM format.
*/
public static String getCACertificates() {
public static @NonNull String getCACertificates() {
try {
KeyStore ks = KeyStore.getInstance("AndroidCAStore");
StringBuilder writer = new StringBuilder();

View File

@ -32,6 +32,7 @@ package org.godotengine.godot.utils;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@ -52,7 +53,6 @@ import java.util.Set;
/**
* This class includes utility functions for Android permissions related operations.
*/
public final class PermissionsUtil {
private static final String TAG = PermissionsUtil.class.getSimpleName();
@ -193,13 +193,13 @@ public final class PermissionsUtil {
/**
* With this function you can get the list of dangerous permissions that have been granted to the Android application.
* @param activity the caller activity for this method.
* @param context the caller context for this method.
* @return granted permissions list
*/
public static String[] getGrantedPermissions(Activity activity) {
public static String[] getGrantedPermissions(Context context) {
String[] manifestPermissions;
try {
manifestPermissions = getManifestPermissions(activity);
manifestPermissions = getManifestPermissions(context);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return new String[0];
@ -215,9 +215,9 @@ public final class PermissionsUtil {
grantedPermissions.add(manifestPermission);
}
} else {
PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission);
PermissionInfo permissionInfo = getPermissionInfo(context, manifestPermission);
int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) == PackageManager.PERMISSION_GRANTED) {
if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(context, manifestPermission) == PackageManager.PERMISSION_GRANTED) {
grantedPermissions.add(manifestPermission);
}
}
@ -232,13 +232,13 @@ public final class PermissionsUtil {
/**
* Check if the given permission is in the AndroidManifest.xml file.
* @param activity the caller activity for this method.
* @param context the caller context for this method.
* @param permission the permession to look for in the manifest file.
* @return "true" if the permission is in the manifest file of the activity, "false" otherwise.
*/
public static boolean hasManifestPermission(Activity activity, String permission) {
public static boolean hasManifestPermission(Context context, String permission) {
try {
for (String p : getManifestPermissions(activity)) {
for (String p : getManifestPermissions(context)) {
if (permission.equals(p))
return true;
}
@ -250,13 +250,13 @@ public final class PermissionsUtil {
/**
* Returns the permissions defined in the AndroidManifest.xml file.
* @param activity the caller activity for this method.
* @param context the caller context for this method.
* @return manifest permissions list
* @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found.
*/
private static String[] getManifestPermissions(Activity activity) throws PackageManager.NameNotFoundException {
PackageManager packageManager = activity.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS);
private static String[] getManifestPermissions(Context context) throws PackageManager.NameNotFoundException {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
if (packageInfo.requestedPermissions == null)
return new String[0];
return packageInfo.requestedPermissions;
@ -264,13 +264,13 @@ public final class PermissionsUtil {
/**
* Returns the information of the desired permission.
* @param activity the caller activity for this method.
* @param context the caller context for this method.
* @param permission the name of the permission.
* @return permission info object
* @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found.
*/
private static PermissionInfo getPermissionInfo(Activity activity, String permission) throws PackageManager.NameNotFoundException {
PackageManager packageManager = activity.getPackageManager();
private static PermissionInfo getPermissionInfo(Context context, String permission) throws PackageManager.NameNotFoundException {
PackageManager packageManager = context.getPackageManager();
return packageManager.getPermissionInfo(permission, 0);
}
}

View File

@ -51,12 +51,22 @@ public class RegularContextFactory implements GLSurfaceView.EGLContextFactory {
private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
private final boolean mUseDebugOpengl;
public RegularContextFactory() {
this(false);
}
public RegularContextFactory(boolean useDebugOpengl) {
this.mUseDebugOpengl = useDebugOpengl;
}
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
Log.w(TAG, "creating OpenGL ES 3.0 context :");
GLUtils.checkEglError(TAG, "Before eglCreateContext", egl);
EGLContext context;
if (GLUtils.use_debug_opengl) {
if (mUseDebugOpengl) {
int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE };
context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
} else {

View File

@ -135,7 +135,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv
os_android = new OS_Android(godot_java, godot_io_java, p_use_apk_expansion);
return godot_java->on_video_init(env);
return true;
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz) {

View File

@ -58,12 +58,10 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
}
// get some Godot method pointers...
_on_video_init = p_env->GetMethodID(godot_class, "onVideoInit", "()Z");
_restart = p_env->GetMethodID(godot_class, "restart", "()V");
_finish = p_env->GetMethodID(godot_class, "forceQuit", "(I)Z");
_set_keep_screen_on = p_env->GetMethodID(godot_class, "setKeepScreenOn", "(Z)V");
_alert = p_env->GetMethodID(godot_class, "alert", "(Ljava/lang/String;Ljava/lang/String;)V");
_get_GLES_version_code = p_env->GetMethodID(godot_class, "getGLESVersionCode", "()I");
_get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;");
_set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V");
_has_clipboard = p_env->GetMethodID(godot_class, "hasClipboard", "()Z");
@ -72,20 +70,15 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;");
_get_ca_certificates = p_env->GetMethodID(godot_class, "getCACertificates", "()Ljava/lang/String;");
_init_input_devices = p_env->GetMethodID(godot_class, "initInputDevices", "()V");
_get_surface = p_env->GetMethodID(godot_class, "getSurface", "()Landroid/view/Surface;");
_is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z");
_vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V");
_get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;");
_on_godot_setup_completed = p_env->GetMethodID(godot_class, "onGodotSetupCompleted", "()V");
_on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V");
_create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)I");
_get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotRenderView;");
_begin_benchmark_measure = p_env->GetMethodID(godot_class, "beginBenchmarkMeasure", "(Ljava/lang/String;)V");
_end_benchmark_measure = p_env->GetMethodID(godot_class, "endBenchmarkMeasure", "(Ljava/lang/String;)V");
_dump_benchmark = p_env->GetMethodID(godot_class, "dumpBenchmark", "(Ljava/lang/String;)V");
// get some Activity method pointers...
_get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;");
_begin_benchmark_measure = p_env->GetMethodID(godot_class, "nativeBeginBenchmarkMeasure", "(Ljava/lang/String;)V");
_end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(Ljava/lang/String;)V");
_dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@ -105,29 +98,6 @@ jobject GodotJavaWrapper::get_activity() {
return activity;
}
jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env) {
if (godot_class) {
if (p_env == nullptr) {
p_env = get_jni_env();
}
ERR_FAIL_NULL_V(p_env, nullptr);
jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class);
return p_env->GetStaticObjectField(godot_class, fid);
} else {
return nullptr;
}
}
jobject GodotJavaWrapper::get_class_loader() {
if (_get_class_loader) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, nullptr);
return env->CallObjectMethod(activity, _get_class_loader);
} else {
return nullptr;
}
}
GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() {
if (godot_view != nullptr) {
return godot_view;
@ -143,17 +113,6 @@ GodotJavaViewWrapper *GodotJavaWrapper::get_godot_view() {
return godot_view;
}
bool GodotJavaWrapper::on_video_init(JNIEnv *p_env) {
if (_on_video_init) {
if (p_env == nullptr) {
p_env = get_jni_env();
}
ERR_FAIL_NULL_V(p_env, false);
return p_env->CallBooleanMethod(godot_instance, _on_video_init);
}
return false;
}
void GodotJavaWrapper::on_godot_setup_completed(JNIEnv *p_env) {
if (_on_godot_setup_completed) {
if (p_env == nullptr) {
@ -212,15 +171,6 @@ void GodotJavaWrapper::alert(const String &p_message, const String &p_title) {
}
}
int GodotJavaWrapper::get_gles_version_code() {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, 0);
if (_get_GLES_version_code) {
return env->CallIntMethod(godot_instance, _get_GLES_version_code);
}
return 0;
}
bool GodotJavaWrapper::has_get_clipboard() {
return _get_clipboard != nullptr;
}
@ -333,26 +283,6 @@ void GodotJavaWrapper::init_input_devices() {
}
}
jobject GodotJavaWrapper::get_surface() {
if (_get_surface) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, nullptr);
return env->CallObjectMethod(godot_instance, _get_surface);
} else {
return nullptr;
}
}
bool GodotJavaWrapper::is_activity_resumed() {
if (_is_activity_resumed) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, false);
return env->CallBooleanMethod(godot_instance, _is_activity_resumed);
} else {
return false;
}
}
void GodotJavaWrapper::vibrate(int p_duration_ms) {
if (_vibrate) {
JNIEnv *env = get_jni_env();

View File

@ -49,12 +49,10 @@ private:
GodotJavaViewWrapper *godot_view = nullptr;
jmethodID _on_video_init = nullptr;
jmethodID _restart = nullptr;
jmethodID _finish = nullptr;
jmethodID _set_keep_screen_on = nullptr;
jmethodID _alert = nullptr;
jmethodID _get_GLES_version_code = nullptr;
jmethodID _get_clipboard = nullptr;
jmethodID _set_clipboard = nullptr;
jmethodID _has_clipboard = nullptr;
@ -63,13 +61,10 @@ private:
jmethodID _get_granted_permissions = nullptr;
jmethodID _get_ca_certificates = nullptr;
jmethodID _init_input_devices = nullptr;
jmethodID _get_surface = nullptr;
jmethodID _is_activity_resumed = nullptr;
jmethodID _vibrate = nullptr;
jmethodID _get_input_fallback_mapping = nullptr;
jmethodID _on_godot_setup_completed = nullptr;
jmethodID _on_godot_main_loop_started = nullptr;
jmethodID _get_class_loader = nullptr;
jmethodID _create_new_godot_instance = nullptr;
jmethodID _get_render_view = nullptr;
jmethodID _begin_benchmark_measure = nullptr;
@ -81,19 +76,15 @@ public:
~GodotJavaWrapper();
jobject get_activity();
jobject get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env = nullptr);
jobject get_class_loader();
GodotJavaViewWrapper *get_godot_view();
bool on_video_init(JNIEnv *p_env = nullptr);
void on_godot_setup_completed(JNIEnv *p_env = nullptr);
void on_godot_main_loop_started(JNIEnv *p_env = nullptr);
void restart(JNIEnv *p_env = nullptr);
bool force_quit(JNIEnv *p_env = nullptr, int p_instance_id = 0);
void set_keep_screen_on(bool p_enabled);
void alert(const String &p_message, const String &p_title);
int get_gles_version_code();
bool has_get_clipboard();
String get_clipboard();
bool has_set_clipboard();
@ -105,8 +96,6 @@ public:
Vector<String> get_granted_permissions() const;
String get_ca_certificates() const;
void init_input_devices();
jobject get_surface();
bool is_activity_resumed();
void vibrate(int p_duration_ms);
String get_input_fallback_mapping();
int create_new_godot_instance(List<String> args);