mirror of
https://github.com/godotengine/godot.git
synced 2024-11-14 16:13:08 +00:00
Merge pull request #76821 from m4gr3d/prototype_godot_service_main
Refactor Godot Android architecture
This commit is contained in:
commit
57919beb05
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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
965
platform/android/java/lib/src/org/godotengine/godot/Godot.kt
Normal file
965
platform/android/java/lib/src/org/godotengine/godot/Godot.kt
Normal 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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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",
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user