summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--platform/android/java/app/src/com/godot/game/GodotApp.java4
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java154
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java1195
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt965
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt167
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java429
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java20
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotHost.java9
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotService.kt54
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java13
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java9
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java11
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java32
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java12
-rw-r--r--platform/android/java_godot_lib_jni.cpp2
-rw-r--r--platform/android/java_godot_wrapper.cpp76
-rw-r--r--platform/android/java_godot_wrapper.h11
20 files changed, 1701 insertions, 1477 deletions
diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java
index 1d2cc05715..9142d767b4 100644
--- a/platform/android/java/app/src/com/godot/game/GodotApp.java
+++ b/platform/android/java/app/src/com/godot/game/GodotApp.java
@@ -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);
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index 64d3d4eca1..7cedfa6888 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -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)
diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
index 3e975449d8..91d272735e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
@@ -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 {}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
deleted file mode 100644
index 9f2dec7317..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ /dev/null
@@ -1,1195 +0,0 @@
-/**************************************************************************/
-/* Godot.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 static android.content.Context.MODE_PRIVATE;
-import static android.content.Context.WINDOW_SERVICE;
-
-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.GodotPlugin;
-import org.godotengine.godot.plugin.GodotPluginRegistry;
-import org.godotengine.godot.tts.GodotTTS;
-import org.godotengine.godot.utils.BenchmarkUtils;
-import org.godotengine.godot.utils.GodotNetUtils;
-import org.godotengine.godot.utils.PermissionsUtil;
-import org.godotengine.godot.xr.XRMode;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.AlertDialog;
-import android.app.PendingIntent;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.content.pm.ConfigurationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Messenger;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.util.Log;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.Surface;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewTreeObserver;
-import android.view.Window;
-import android.view.WindowInsets;
-import android.view.WindowInsetsAnimation;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.FrameLayout;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.Keep;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-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.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.security.MessageDigest;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-
-public class Godot extends Fragment implements SensorEventListener, IDownloaderClient {
- private static final String TAG = Godot.class.getSimpleName();
-
- private IStub mDownloaderClientStub;
- private TextView mStatusText;
- private TextView mProgressFraction;
- private TextView mProgressPercent;
- private TextView mAverageSpeed;
- private TextView mTimeRemaining;
- private ProgressBar mPB;
- private ClipboardManager mClipboard;
-
- private View mDashboard;
- private View mCellMessage;
-
- private Button mPauseButton;
- private Button mWiFiSettingsButton;
-
- private XRMode xrMode = XRMode.REGULAR;
- private boolean use_immersive = false;
- private boolean use_debug_opengl = false;
- private boolean mStatePaused;
- private boolean activityResumed;
- private int mState;
-
- private GodotHost godotHost;
- private GodotPluginRegistry pluginRegistry;
-
- 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);
- }
-
- private String[] command_line;
- private boolean use_apk_expansion;
-
- private ViewGroup containerLayout;
- public GodotRenderView mRenderView;
- private boolean godot_initialized = false;
-
- private SensorManager mSensorManager;
- private Sensor mAccelerometer;
- private Sensor mGravity;
- private Sensor mMagnetometer;
- private Sensor mGyroscope;
-
- public GodotIO io;
- public GodotNetUtils netUtils;
- public GodotTTS tts;
- private DirectoryAccessHandler directoryAccessHandler;
- private FileAccessHandler fileAccessHandler;
-
- public interface ResultCallback {
- void callback(int requestCode, int resultCode, Intent data);
- }
- public ResultCallback result_callback;
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- if (getParentFragment() instanceof GodotHost) {
- godotHost = (GodotHost)getParentFragment();
- } else if (getActivity() instanceof GodotHost) {
- godotHost = (GodotHost)getActivity();
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- godotHost = null;
- }
-
- @CallSuper
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (result_callback != null) {
- result_callback.callback(requestCode, resultCode, data);
- result_callback = null;
- }
-
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- plugin.onMainActivityResult(requestCode, resultCode, data);
- }
- }
-
- @CallSuper
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- plugin.onMainRequestPermissionsResult(requestCode, permissions, grantResults);
- }
-
- for (int i = 0; i < permissions.length; i++) {
- GodotLib.requestPermissionResult(permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED);
- }
- }
-
- /**
- * Invoked on the render thread when the Godot setup is complete.
- */
- @CallSuper
- protected void onGodotSetupCompleted() {
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- plugin.onGodotSetupCompleted();
- }
-
- if (godotHost != null) {
- godotHost.onGodotSetupCompleted();
- }
- }
-
- /**
- * Invoked on the render thread when the Godot main loop has started.
- */
- @CallSuper
- protected void onGodotMainLoopStarted() {
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- plugin.onGodotMainLoopStarted();
- }
-
- if (godotHost != null) {
- godotHost.onGodotMainLoopStarted();
- }
- }
-
- /**
- * Used by the native code (java_godot_lib_jni.cpp) to complete initialization of the GLSurfaceView view and renderer.
- */
- @Keep
- private boolean onVideoInit() {
- final Activity activity = requireActivity();
- containerLayout = new FrameLayout(activity);
- containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-
- // GodotEditText layout
- GodotEditText editText = new GodotEditText(activity);
- editText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
- (int)getResources().getDimension(R.dimen.text_edit_height)));
- // ...add to FrameLayout
- containerLayout.addView(editText);
-
- tts = new GodotTTS(activity);
-
- if (!GodotLib.setup(command_line, tts)) {
- 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 false;
- }
-
- if (usesVulkan()) {
- if (!meetsVulkanRequirements(activity.getPackageManager())) {
- alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit);
- return false;
- }
- mRenderView = new GodotVulkanRenderView(activity, this);
- } else {
- // Fallback to openGl
- mRenderView = new GodotGLRenderView(activity, this, xrMode, use_debug_opengl);
- }
-
- View view = mRenderView.getView();
- containerLayout.addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- editText.setView(mRenderView);
- 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.
- final View decorView = activity.getWindow().getDecorView();
- decorView.setWindowInsetsAnimationCallback(new WindowInsetsAnimation.Callback(WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP) {
- int startBottom, endBottom;
- @Override
- public void onPrepare(@NonNull WindowInsetsAnimation animation) {
- startBottom = decorView.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
- }
-
- @NonNull
- @Override
- public WindowInsetsAnimation.Bounds onStart(@NonNull WindowInsetsAnimation animation, @NonNull WindowInsetsAnimation.Bounds bounds) {
- endBottom = decorView.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
- return bounds;
- }
-
- @NonNull
- @Override
- public WindowInsets onProgress(@NonNull WindowInsets windowInsets, @NonNull List<WindowInsetsAnimation> list) {
- // Find the IME animation.
- WindowInsetsAnimation imeAnimation = null;
- for (WindowInsetsAnimation animation : list) {
- if ((animation.getTypeMask() & WindowInsets.Type.ime()) != 0) {
- imeAnimation = animation;
- break;
- }
- }
- // Update keyboard height based on IME animation.
- if (imeAnimation != null) {
- float interpolatedFraction = imeAnimation.getInterpolatedFraction();
- // Linear interpolation between start and end values.
- float keyboardHeight = startBottom * (1.0f - interpolatedFraction) + endBottom * interpolatedFraction;
- GodotLib.setVirtualKeyboardHeight((int)keyboardHeight);
- }
- return windowInsets;
- }
-
- @Override
- public void onEnd(@NonNull WindowInsetsAnimation animation) {
- }
- });
- } else {
- // Infer the virtual keyboard height using visible area.
- view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- // Don't allocate a new Rect every time the callback is called.
- final Rect visibleSize = new Rect();
-
- @Override
- public void onGlobalLayout() {
- final SurfaceView view = mRenderView.getView();
- view.getWindowVisibleDisplayFrame(visibleSize);
- final int keyboardHeight = view.getHeight() - visibleSize.bottom;
- GodotLib.setVirtualKeyboardHeight(keyboardHeight);
- }
- });
- }
-
- mRenderView.queueOnRenderThread(() -> {
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- plugin.onRegisterPluginWithGodotNative();
- }
- setKeepScreenOn(Boolean.parseBoolean(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on")));
- });
-
- // Include the returned non-null views in the Godot view hierarchy.
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- View pluginView = plugin.onMainCreate(activity);
- if (pluginView != null) {
- if (plugin.shouldBeOnTop()) {
- containerLayout.addView(pluginView);
- } else {
- containerLayout.addView(pluginView, 0);
- }
- }
- }
- return true;
- }
-
- /**
- * Returns true if `Vulkan` is used for rendering.
- */
- private boolean usesVulkan() {
- final String renderer = GodotLib.getGlobal("rendering/renderer/rendering_method");
- final String renderingDevice = GodotLib.getGlobal("rendering/rendering_device/driver");
- return ("forward_plus".equals(renderer) || "mobile".equals(renderer)) && "vulkan".equals(renderingDevice);
- }
-
- /**
- * Returns true if the device meets the base requirements for Vulkan support, false otherwise.
- */
- private boolean meetsVulkanRequirements(@Nullable PackageManager packageManager) {
- 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;
- }
-
- public void setKeepScreenOn(final boolean p_enabled) {
- runOnUiThread(() -> {
- if (p_enabled) {
- getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- } else {
- getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
- });
- }
-
- /**
- * Used by the native code (java_godot_wrapper.h) to vibrate the device.
- * @param durationMs
- */
- @SuppressLint("MissingPermission")
- @Keep
- private void vibrate(int durationMs) {
- if (durationMs > 0 && requestPermission("VIBRATE")) {
- Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE);
- if (v != null) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE));
- } else {
- // deprecated in API 26
- v.vibrate(durationMs);
- }
- }
- }
- }
-
- public void restart() {
- if (godotHost != null) {
- godotHost.onGodotRestartRequested(this);
- }
- }
-
- public void alert(final String message, final String title) {
- alert(message, title, null);
- }
-
- private void alert(@StringRes int messageResId, @StringRes int titleResId, @Nullable Runnable okCallback) {
- Resources res = getResources();
- alert(res.getString(messageResId), res.getString(titleResId), okCallback);
- }
-
- private void alert(final String message, final String title, @Nullable Runnable okCallback) {
- final Activity activity = getActivity();
- runOnUiThread(() -> {
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setMessage(message).setTitle(title);
- builder.setPositiveButton(
- "OK",
- (dialog, id) -> {
- if (okCallback != null) {
- okCallback.run();
- }
- dialog.cancel();
- });
- AlertDialog dialog = builder.create();
- dialog.show();
- });
- }
-
- public int getGLESVersionCode() {
- ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
- ConfigurationInfo deviceInfo = am.getDeviceConfigurationInfo();
- return deviceInfo.reqGlEsVersion;
- }
-
- @CallSuper
- protected String[] getCommandLine() {
- String[] original = parseCommandLine();
- String[] updated;
- List<String> hostCommandLine = godotHost != null ? godotHost.getCommandLine() : null;
- if (hostCommandLine == null || hostCommandLine.isEmpty()) {
- updated = original;
- } else {
- updated = Arrays.copyOf(original, original.length + hostCommandLine.size());
- for (int i = 0; i < hostCommandLine.size(); i++) {
- updated[original.length + i] = hostCommandLine.get(i);
- }
- }
- return updated;
- }
-
- private String[] parseCommandLine() {
- InputStream is;
- try {
- is = getActivity().getAssets().open("_cl_");
- byte[] len = new byte[4];
- int r = is.read(len);
- if (r < 4) {
- return new String[0];
- }
- int argc = ((int)(len[3] & 0xFF) << 24) | ((int)(len[2] & 0xFF) << 16) | ((int)(len[1] & 0xFF) << 8) | ((int)(len[0] & 0xFF));
- String[] cmdline = new String[argc];
-
- for (int i = 0; i < argc; i++) {
- r = is.read(len);
- if (r < 4) {
- return new String[0];
- }
- int strlen = ((int)(len[3] & 0xFF) << 24) | ((int)(len[2] & 0xFF) << 16) | ((int)(len[1] & 0xFF) << 8) | ((int)(len[0] & 0xFF));
- if (strlen > 65535) {
- return new String[0];
- }
- byte[] arg = new byte[strlen];
- r = is.read(arg);
- if (r == strlen) {
- cmdline[i] = new String(arg, "UTF-8");
- }
- }
- return cmdline;
- } catch (Exception e) {
- // The _cl_ file can be missing with no adverse effect
- return new String[0];
- }
- }
-
- /**
- * Used by the native code (java_godot_wrapper.h) to check whether the activity is resumed or paused.
- */
- @Keep
- private boolean isActivityResumed() {
- return activityResumed;
- }
-
- /**
- * Used by the native code (java_godot_wrapper.h) to access the Android surface.
- */
- @Keep
- private Surface getSurface() {
- return mRenderView.getView().getHolder().getSurface();
- }
-
- /**
- * 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 String getInputFallbackMapping() {
- return xrMode.inputFallbackMapping;
- }
-
- String expansion_pack_path;
-
- private void initializeGodot() {
- if (expansion_pack_path != null) {
- String[] new_cmdline;
- int cll = 0;
- if (command_line != null) {
- new_cmdline = new String[command_line.length + 2];
- cll = command_line.length;
- for (int i = 0; i < command_line.length; i++) {
- new_cmdline[i] = command_line[i];
- }
- } else {
- new_cmdline = new String[2];
- }
-
- new_cmdline[cll] = "--main-pack";
- new_cmdline[cll + 1] = expansion_pack_path;
- command_line = new_cmdline;
- }
-
- final Activity activity = getActivity();
- io = new GodotIO(activity);
- netUtils = new GodotNetUtils(activity);
- Context context = getContext();
- directoryAccessHandler = new DirectoryAccessHandler(context);
- fileAccessHandler = new FileAccessHandler(context);
- mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
- mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
- mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
- mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
-
- godot_initialized = GodotLib.initialize(activity,
- this,
- activity.getAssets(),
- io,
- netUtils,
- directoryAccessHandler,
- fileAccessHandler,
- use_apk_expansion);
-
- result_callback = null;
- }
-
- @Override
- public void onServiceConnected(Messenger m) {
- IDownloaderService remoteService = DownloaderServiceMarshaller.CreateProxy(m);
- remoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- BenchmarkUtils.beginBenchmarkMeasure("Godot::onCreate");
- super.onCreate(icicle);
-
- final Activity activity = getActivity();
- Window window = activity.getWindow();
- window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
- mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE);
- pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this);
-
- // check for apk expansion API
- boolean md5mismatch = false;
- command_line = getCommandLine();
- String main_pack_md5 = null;
- String main_pack_key = null;
-
- List<String> new_args = new LinkedList<>();
-
- for (int i = 0; i < command_line.length; i++) {
- boolean has_extra = i < command_line.length - 1;
- if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) {
- xrMode = XRMode.REGULAR;
- } else if (command_line[i].equals(XRMode.OPENXR.cmdLineArg)) {
- xrMode = XRMode.OPENXR;
- } else if (command_line[i].equals("--debug_opengl")) {
- use_debug_opengl = true;
- } else if (command_line[i].equals("--use_immersive")) {
- use_immersive = true;
- window.getDecorView().setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar
- View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
- UiChangeListener();
- } else if (command_line[i].equals("--use_apk_expansion")) {
- use_apk_expansion = true;
- } else if (has_extra && command_line[i].equals("--apk_expansion_md5")) {
- main_pack_md5 = command_line[i + 1];
- i++;
- } else if (has_extra && command_line[i].equals("--apk_expansion_key")) {
- main_pack_key = command_line[i + 1];
- SharedPreferences prefs = activity.getSharedPreferences("app_data_keys",
- MODE_PRIVATE);
- Editor editor = prefs.edit();
- editor.putString("store_public_key", main_pack_key);
-
- editor.apply();
- i++;
- } else if (command_line[i].equals("--benchmark")) {
- BenchmarkUtils.setUseBenchmark(true);
- new_args.add(command_line[i]);
- } else if (has_extra && command_line[i].equals("--benchmark-file")) {
- BenchmarkUtils.setUseBenchmark(true);
- new_args.add(command_line[i]);
-
- // Retrieve the filepath
- BenchmarkUtils.setBenchmarkFile(command_line[i + 1]);
- new_args.add(command_line[i + 1]);
-
- i++;
- } else if (command_line[i].trim().length() != 0) {
- new_args.add(command_line[i]);
- }
- }
-
- if (new_args.isEmpty()) {
- command_line = null;
- } else {
- command_line = new_args.toArray(new String[new_args.size()]);
- }
- if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) {
- // check that environment is ok!
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- // show popup and die
- }
-
- // Build the full path to the app's expansion files
- try {
- expansion_pack_path = Helpers.getSaveFilePath(getContext());
- expansion_pack_path += "/main." + activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionCode + "." + activity.getPackageName() + ".obb";
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- File f = new File(expansion_pack_path);
-
- boolean pack_valid = true;
-
- if (!f.exists()) {
- pack_valid = false;
-
- } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) {
- pack_valid = false;
- try {
- f.delete();
- } catch (Exception e) {
- }
- }
-
- if (!pack_valid) {
- 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;
- }
- } catch (NameNotFoundException e) {
- // TODO Auto-generated catch block
- }
- }
- }
-
- mCurrentIntent = activity.getIntent();
-
- initializeGodot();
- BenchmarkUtils.endBenchmarkMeasure("Godot::onCreate");
- }
-
- @Override
- public View onCreateView(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 containerLayout;
- }
-
- @Override
- public void onDestroy() {
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- plugin.onMainDestroy();
- }
-
- GodotLib.ondestroy();
-
- super.onDestroy();
-
- forceQuit();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- activityResumed = false;
-
- if (!godot_initialized) {
- if (null != mDownloaderClientStub) {
- mDownloaderClientStub.disconnect(getActivity());
- }
- return;
- }
- mRenderView.onActivityPaused();
-
- mSensorManager.unregisterListener(this);
-
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- plugin.onMainPause();
- }
- }
-
- public boolean hasClipboard() {
- return mClipboard.hasPrimaryClip();
- }
-
- public String getClipboard() {
- ClipData clipData = mClipboard.getPrimaryClip();
- if (clipData == null)
- return "";
- CharSequence text = clipData.getItemAt(0).getText();
- if (text == null)
- return "";
- return text.toString();
- }
-
- public void setClipboard(String p_text) {
- ClipData clip = ClipData.newPlainText("myLabel", p_text);
- mClipboard.setPrimaryClip(clip);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- activityResumed = true;
- if (!godot_initialized) {
- if (null != mDownloaderClientStub) {
- mDownloaderClientStub.connect(getActivity());
- }
- return;
- }
-
- mRenderView.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 (use_immersive) {
- Window window = getActivity().getWindow();
- window.getDecorView().setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar
- View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
- }
-
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- plugin.onMainResume();
- }
- }
-
- public void UiChangeListener() {
- final View decorView = getActivity().getWindow().getDecorView();
- decorView.setOnSystemUiVisibilityChangeListener(visibility -> {
- if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
- decorView.setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_FULLSCREEN |
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
- }
- });
- }
-
- public float[] getRotatedValues(float values[]) {
- if (values == null || values.length != 3) {
- return values;
- }
-
- Display display =
- ((WindowManager)getActivity().getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
- int displayRotation = display.getRotation();
-
- float[] rotatedValues = new float[3];
- switch (displayRotation) {
- case Surface.ROTATION_0:
- rotatedValues[0] = values[0];
- rotatedValues[1] = values[1];
- rotatedValues[2] = values[2];
- break;
- case Surface.ROTATION_90:
- rotatedValues[0] = -values[1];
- rotatedValues[1] = values[0];
- rotatedValues[2] = values[2];
- break;
- case Surface.ROTATION_180:
- rotatedValues[0] = -values[0];
- rotatedValues[1] = -values[1];
- rotatedValues[2] = values[2];
- break;
- case Surface.ROTATION_270:
- rotatedValues[0] = values[1];
- rotatedValues[1] = -values[0];
- rotatedValues[2] = values[2];
- break;
- }
-
- return rotatedValues;
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- if (mRenderView == null) {
- return;
- }
-
- final int typeOfSensor = event.sensor.getType();
- switch (typeOfSensor) {
- case Sensor.TYPE_ACCELEROMETER: {
- float[] rotatedValues = getRotatedValues(event.values);
- mRenderView.queueOnRenderThread(() -> {
- GodotLib.accelerometer(-rotatedValues[0], -rotatedValues[1], -rotatedValues[2]);
- });
- break;
- }
- case Sensor.TYPE_GRAVITY: {
- float[] rotatedValues = getRotatedValues(event.values);
- mRenderView.queueOnRenderThread(() -> {
- GodotLib.gravity(-rotatedValues[0], -rotatedValues[1], -rotatedValues[2]);
- });
- break;
- }
- case Sensor.TYPE_MAGNETIC_FIELD: {
- float[] rotatedValues = getRotatedValues(event.values);
- mRenderView.queueOnRenderThread(() -> {
- GodotLib.magnetometer(-rotatedValues[0], -rotatedValues[1], -rotatedValues[2]);
- });
- break;
- }
- case Sensor.TYPE_GYROSCOPE: {
- float[] rotatedValues = getRotatedValues(event.values);
- mRenderView.queueOnRenderThread(() -> {
- GodotLib.gyroscope(rotatedValues[0], rotatedValues[1], rotatedValues[2]);
- });
- break;
- }
- }
- }
-
- @Override
- public final void onAccuracyChanged(Sensor sensor, int accuracy) {
- // Do something here if sensor accuracy changes.
- }
-
- public void onBackPressed() {
- boolean shouldQuit = true;
-
- for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
- if (plugin.onMainBackPressed()) {
- shouldQuit = false;
- }
- }
-
- if (shouldQuit && mRenderView != null) {
- mRenderView.queueOnRenderThread(GodotLib::back);
- }
- }
-
- /**
- * Queue a runnable to be run on the render thread.
- * <p>
- * This must be called after the render thread has started.
- */
- public final void runOnRenderThread(@NonNull Runnable action) {
- if (mRenderView != null) {
- mRenderView.queueOnRenderThread(action);
- }
- }
-
- public final void runOnUiThread(@NonNull Runnable action) {
- if (getActivity() != null) {
- getActivity().runOnUiThread(action);
- }
- }
-
- private void forceQuit() {
- // TODO: This is a temp solution. The proper fix will involve tracking down and properly shutting down each
- // native Godot components that is started in Godot#onVideoInit.
- forceQuit(0);
- }
-
- @Keep
- private boolean forceQuit(int instanceId) {
- if (godotHost == null) {
- return false;
- }
- if (instanceId == 0) {
- godotHost.onGodotForceQuit(this);
- return true;
- } else {
- return godotHost.onGodotForceQuit(instanceId);
- }
- }
-
- private boolean obbIsCorrupted(String f, String main_pack_md5) {
- try {
- InputStream fis = new FileInputStream(f);
-
- // Create MD5 Hash
- byte[] buffer = new byte[16384];
-
- MessageDigest complete = MessageDigest.getInstance("MD5");
- int numRead;
- do {
- numRead = fis.read(buffer);
- if (numRead > 0) {
- complete.update(buffer, 0, numRead);
- }
- } while (numRead != -1);
-
- fis.close();
- byte[] messageDigest = complete.digest();
-
- // Create Hex String
- StringBuilder hexString = new StringBuilder();
- for (byte b : messageDigest) {
- String s = Integer.toHexString(0xFF & b);
- if (s.length() == 1) {
- s = "0" + s;
- }
- hexString.append(s);
- }
- String md5str = hexString.toString();
-
- if (!md5str.equals(main_pack_md5)) {
- return true;
- }
- return false;
- } catch (Exception e) {
- e.printStackTrace();
- return true;
- }
- }
-
- public boolean requestPermission(String p_name) {
- return PermissionsUtil.requestPermission(p_name, getActivity());
- }
-
- public boolean requestPermissions() {
- return PermissionsUtil.requestManifestPermissions(getActivity());
- }
-
- public String[] getGrantedPermissions() {
- return PermissionsUtil.getGrantedPermissions(getActivity());
- }
-
- @Keep
- private String getCACertificates() {
- return GodotNetUtils.getCACertificates();
- }
-
- /**
- * 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;
- initializeGodot();
- 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));
- }
-
- public void initInputDevices() {
- mRenderView.initInputDevices();
- }
-
- @Keep
- public GodotRenderView getRenderView() { // used by native side to get renderView
- return mRenderView;
- }
-
- @Keep
- public DirectoryAccessHandler getDirectoryAccessHandler() {
- return directoryAccessHandler;
- }
-
- @Keep
- public FileAccessHandler getFileAccessHandler() {
- return fileAccessHandler;
- }
-
- @Keep
- private int createNewGodotInstance(String[] args) {
- if (godotHost != null) {
- return godotHost.onNewGodotInstanceRequested(args);
- }
- return 0;
- }
-
- @Keep
- private void beginBenchmarkMeasure(String label) {
- BenchmarkUtils.beginBenchmarkMeasure(label);
- }
-
- @Keep
- private void endBenchmarkMeasure(String label) {
- BenchmarkUtils.endBenchmarkMeasure(label);
- }
-
- @Keep
- private void dumpBenchmark(String benchmarkFile) {
- BenchmarkUtils.dumpBenchmark(fileAccessHandler, benchmarkFile);
- }
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
new file mode 100644
index 0000000000..23de01a191
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -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)
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
new file mode 100644
index 0000000000..4636f753af
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
@@ -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()
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
new file mode 100644
index 0000000000..9a8b10ea3e
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
@@ -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;
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
index b465377743..52350c12a6 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java
@@ -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);
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
index 7700b9b628..e5333085dd 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
@@ -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();
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
index 00243dab2a..ebf3a6b2fb 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java
@@ -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();
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt
new file mode 100644
index 0000000000..68cd2c1358
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt
@@ -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()
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
index 681e182adb..48708152be 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java
@@ -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
diff --git a/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java b/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java
index edace53e7f..dce6753b7a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java
@@ -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);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
index 7db02968bb..2c7b73ae4d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GLUtils.java
@@ -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",
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
index c31d56a3e1..dca190a2fc 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java
@@ -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();
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index a94188c405..8353fc8dc6 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -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);
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
index 1a126ff765..01ee41e30b 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/xr/regular/RegularContextFactory.java
@@ -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 {
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index b54491e0e1..74605e3377 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -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) {
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 862d9f0436..79ba2528ba 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -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();
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 1efdffd71b..ba42d5dccd 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -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);