Godot Android re-architecture
Decouples the Godot java entry point from the Android Fragment component. This enables the Godot component to be more easily reused across different types of Android components including Activities and Services.
This commit is contained in:
parent
00c782d959
commit
29bbc17b48
@ -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 {}
|
||||
|
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()
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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