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 1d2cc05715a..9142d767b4b 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 64d3d4eca13..7cedfa68886 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, + permissions: Array, 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 3e975449d83..91d272735e7 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 9f2dec73172..00000000000 --- 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 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 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 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. - *

- * 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 00000000000..23de01a1912 --- /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 = ArrayList() + 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 = 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): 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, + 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 { + val original: MutableList = parseCommandLine() + val hostCommandLine = primaryHost?.commandLine + if (hostCommandLine != null && hostCommandLine.isNotEmpty()) { + original.addAll(hostCommandLine) + } + return original + } + + private fun parseCommandLine(): MutableList { + 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(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? { + 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): 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 00000000000..4636f753af0 --- /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, 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 00000000000..9a8b10ea3ee --- /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 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 b4653777434..52350c12a6d 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 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 7700b9b6280..e5333085dd3 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 00243dab2a9..ebf3a6b2fb7 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 00000000000..68cd2c13586 --- /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 681e182adbc..48708152bec 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 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 edace53e7ff..dce6753b7a3 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 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(); 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 7db02968bbd..2c7b73ae4d7 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 c31d56a3e17..dca190a2fc9 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 a94188c405f..8353fc8dc6b 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 1a126ff7651..01ee41e30b2 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 b54491e0e1f..74605e33774 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 862d9f0436e..79ba2528ba7 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 1efdffd71bf..ba42d5dccd6 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 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 args);