godot/platform/android/java/lib/src/org/godotengine/godot/Godot.java
Rémi Verschelde 3b11e33a09
clang-format: Disable alignment of operands, too unreliable
Sets `AlignOperands` to `DontAlign`.

`clang-format` developers seem to mostly care about space-based indentation and
every other version of clang-format breaks the bad mismatch of tabs and spaces
that it seems to use for operand alignment. So it's better without, so that it
respects our two-tabs `ContinuationIndentWidth`.
2021-10-28 15:19:35 +02:00

989 lines
31 KiB
Java

/*************************************************************************/
/* Godot.java */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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.plugin.GodotPlugin;
import org.godotengine.godot.plugin.GodotPluginRegistry;
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.ComponentName;
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.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.view.Display;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.CallSuper;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
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 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_32_bits = false;
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 static GodotIO io;
public static GodotNetUtils netUtils;
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 void onVideoInit() {
final Activity activity = getActivity();
containerLayout = new FrameLayout(activity);
containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
// GodotEditText layout
GodotEditText editText = new GodotEditText(activity);
editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
(int)getResources().getDimension(R.dimen.text_edit_height)));
// ...add to FrameLayout
containerLayout.addView(editText);
GodotLib.setup(command_line);
final String videoDriver = GodotLib.getGlobal("rendering/driver/driver_name");
if (videoDriver.equals("Vulkan")) {
mRenderView = new GodotVulkanRenderView(activity, this);
} else {
mRenderView = new GodotGLRenderView(activity, this, xrMode, use_32_bits,
use_debug_opengl);
}
View view = mRenderView.getView();
containerLayout.addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
editText.setView(mRenderView);
io.setEdit(editText);
view.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
Point fullSize = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(fullSize);
Rect gameSize = new Rect();
mRenderView.getView().getWindowVisibleDisplayFrame(gameSize);
final int keyboardHeight = fullSize.y - gameSize.bottom;
GodotLib.setVirtualKeyboardHeight(keyboardHeight);
});
mRenderView.queueOnRenderThread(() -> {
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
plugin.onRegisterPluginWithGodotNative();
}
setKeepScreenOn("True".equals(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);
}
}
}
}
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) {
final Activity activity = getActivity();
runOnUiThread(() -> {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(message).setTitle(title);
builder.setPositiveButton(
"OK",
(dialog, id) -> dialog.cancel());
AlertDialog dialog = builder.create();
dialog.show();
});
}
public int getGLESVersionCode() {
ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo deviceInfo = am.getDeviceConfigurationInfo();
return deviceInfo.reqGlEsVersion;
}
@CallSuper
protected String[] getCommandLine() {
String[] original = parseCommandLine();
String[] updated;
List<String> hostCommandLine = godotHost != null ? godotHost.getCommandLine() : null;
if (hostCommandLine == null || hostCommandLine.isEmpty()) {
updated = original;
} else {
updated = Arrays.copyOf(original, original.length + hostCommandLine.size());
for (int i = 0; i < hostCommandLine.size(); i++) {
updated[original.length + i] = hostCommandLine.get(i);
}
}
return updated;
}
private String[] parseCommandLine() {
InputStream is;
try {
is = getActivity().getAssets().open("_cl_");
byte[] len = new byte[4];
int r = is.read(len);
if (r < 4) {
return new String[0];
}
int argc = ((int)(len[3] & 0xFF) << 24) | ((int)(len[2] & 0xFF) << 16) | ((int)(len[1] & 0xFF) << 8) | ((int)(len[0] & 0xFF));
String[] cmdline = new String[argc];
for (int i = 0; i < argc; i++) {
r = is.read(len);
if (r < 4) {
return new String[0];
}
int strlen = ((int)(len[3] & 0xFF) << 24) | ((int)(len[2] & 0xFF) << 16) | ((int)(len[1] & 0xFF) << 8) | ((int)(len[0] & 0xFF));
if (strlen > 65535) {
return new String[0];
}
byte[] arg = new byte[strlen];
r = is.read(arg);
if (r == strlen) {
cmdline[i] = new String(arg, "UTF-8");
}
}
return cmdline;
} catch (Exception e) {
e.printStackTrace();
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);
GodotLib.io = io;
netUtils = new GodotNetUtils(activity);
mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
mSensorManager.registerListener(this, mGravity, SensorManager.SENSOR_DELAY_GAME);
mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME);
mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME);
GodotLib.initialize(activity, this, activity.getAssets(), use_apk_expansion);
result_callback = null;
godot_initialized = true;
}
@Override
public void onServiceConnected(Messenger m) {
IDownloaderService remoteService = DownloaderServiceMarshaller.CreateProxy(m);
remoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final Activity activity = getActivity();
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE);
pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this);
// check for apk expansion API
boolean md5mismatch = false;
command_line = getCommandLine();
String main_pack_md5 = null;
String main_pack_key = null;
List<String> new_args = new LinkedList<>();
for (int i = 0; i < command_line.length; i++) {
boolean has_extra = i < command_line.length - 1;
if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) {
xrMode = XRMode.REGULAR;
} else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) {
xrMode = XRMode.OVR;
} else if (command_line[i].equals("--use_depth_32")) {
use_32_bits = true;
} else if (command_line[i].equals("--debug_opengl")) {
use_debug_opengl = true;
} else if (command_line[i].equals("--use_immersive")) {
use_immersive = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+
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].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 = 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();
}
@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 String getClipboard() {
String copiedText = "";
if (mClipboard.getPrimaryClip() != null) {
ClipData.Item item = mClipboard.getPrimaryClip().getItemAt(0);
copiedText = item.getText().toString();
}
return copiedText;
}
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 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+
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) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
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);
}
}
});
}
@Override
public void onSensorChanged(SensorEvent event) {
Display display =
((WindowManager)getActivity().getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
int displayRotation = display.getRotation();
float[] adjustedValues = new float[3];
final int[][] axisSwap = {
{ 1, -1, 0, 1 }, // ROTATION_0
{ -1, -1, 1, 0 }, // ROTATION_90
{ -1, 1, 0, 1 }, // ROTATION_180
{ 1, 1, 1, 0 }
}; // ROTATION_270
final int[] as = axisSwap[displayRotation];
adjustedValues[0] = (float)as[0] * event.values[as[2]];
adjustedValues[1] = (float)as[1] * event.values[as[3]];
adjustedValues[2] = event.values[2];
final float x = adjustedValues[0];
final float y = adjustedValues[1];
final float z = adjustedValues[2];
final int typeOfSensor = event.sensor.getType();
if (mRenderView != null) {
mRenderView.queueOnRenderThread(() -> {
if (typeOfSensor == Sensor.TYPE_ACCELEROMETER) {
GodotLib.accelerometer(-x, y, -z);
}
if (typeOfSensor == Sensor.TYPE_GRAVITY) {
GodotLib.gravity(-x, y, -z);
}
if (typeOfSensor == Sensor.TYPE_MAGNETIC_FIELD) {
GodotLib.magnetometer(-x, y, -z);
}
if (typeOfSensor == Sensor.TYPE_GYROSCOPE) {
GodotLib.gyroscope(x, -y, z);
}
});
}
}
@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do something here if sensor accuracy changes.
}
/*
@Override public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode()==KeyEvent.KEYCODE_BACK) {
System.out.printf("** BACK REQUEST!\n");
GodotLib.quit();
return true;
}
System.out.printf("** OTHER KEY!\n");
return false;
}
*/
public void onBackPressed() {
boolean shouldQuit = true;
for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) {
if (plugin.onMainBackPressed()) {
shouldQuit = false;
}
}
if (shouldQuit && mRenderView != null) {
mRenderView.queueOnRenderThread(GodotLib::back);
}
}
/**
* Queue a runnable to be run on the render thread.
* <p>
* This must be called after the render thread has started.
*/
public final void runOnRenderThread(@NonNull Runnable action) {
if (mRenderView != null) {
mRenderView.queueOnRenderThread(action);
}
}
public final void runOnUiThread(@NonNull Runnable action) {
if (getActivity() != null) {
getActivity().runOnUiThread(action);
}
}
private void forceQuit() {
// TODO: This is a temp solution. The proper fix will involve tracking down and properly shutting down each
// native Godot components that is started in Godot#onVideoInit.
if (godotHost != null) {
godotHost.onGodotForceQuit(this);
}
}
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 (int i = 0; i < messageDigest.length; i++) {
String s = Integer.toHexString(0xFF & messageDigest[i]);
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());
}
/**
* 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
private GodotRenderView getRenderView() { // used by native side to get renderView
return mRenderView;
}
}