mirror of
https://github.com/love2d/love-android.git
synced 2026-08-12 08:31:04 +02:00
Rewrite GameActivity.java
This commit is contained in:
@@ -1,14 +1,403 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2022 LOVE Development Team
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
package org.love2d.android;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Rect;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class GameActivity extends SDLActivity {
|
||||
private static final String TAG = "GameActivity";
|
||||
|
||||
protected Vibrator vibrator;
|
||||
protected boolean shortEdgesMode;
|
||||
private GameInfo currentGameInfo;
|
||||
private int delayedFd = -1;
|
||||
|
||||
private static native void nativeSetDefaultStreamValues(int sampleRate, int framesPerBurst);
|
||||
|
||||
@Override
|
||||
protected String getMainSharedObject() {
|
||||
String[] libs = getLibraries();
|
||||
// Since Lollipop, you can simply pass "libname.so" to dlopen
|
||||
// and it will resolve correct paths and load correct library.
|
||||
// This is mandatory for extractNativeLibs=false support in
|
||||
// Marshmallow.
|
||||
return "lib" + libs[libs.length - 1] + ".so";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[] {
|
||||
"c++_shared",
|
||||
"SDL2",
|
||||
"openal",
|
||||
"luajit",
|
||||
"love",
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "started");
|
||||
|
||||
if (checkCallingOrSelfPermission(Manifest.permission.VIBRATE) == PackageManager.PERMISSION_GRANTED) {
|
||||
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
|
||||
}
|
||||
|
||||
currentGameInfo = new GameInfo();
|
||||
|
||||
handleIntent(getIntent());
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set low-latency audio values
|
||||
nativeSetDefaultStreamValues(getAudioFreq(), getAudioSMP());
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 28) {
|
||||
WindowManager.LayoutParams attr = getWindow().getAttributes();
|
||||
attr.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
|
||||
shortEdgesMode = false;
|
||||
}
|
||||
|
||||
if (delayedFd != -1) {
|
||||
// This delayed fd is only sent if an embedded game is present.
|
||||
SDLActivity.onNativeDropFile("love2d://fd/" + delayedFd);
|
||||
delayedFd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (vibrator != null) {
|
||||
Log.d(TAG, "Cancelling vibration");
|
||||
vibrator.cancel();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (vibrator != null) {
|
||||
Log.d(TAG, "Cancelling vibration");
|
||||
vibrator.cancel();
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Keep
|
||||
public boolean hasEmbeddedGame() {
|
||||
AssetManager am = getAssets();
|
||||
InputStream inputStream;
|
||||
|
||||
try {
|
||||
// Prioritize main.lua in assets folder
|
||||
inputStream = am.open("main.lua");
|
||||
} catch (IOException e) {
|
||||
// Not found, try game.love in assets folder
|
||||
try {
|
||||
inputStream = am.open("game.love");
|
||||
} catch (IOException e2) {
|
||||
// Not found
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
return inputStream != null;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public GameInfo getGameInfo() {
|
||||
return currentGameInfo;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void vibrate(double seconds) {
|
||||
if (vibrator != null) {
|
||||
long duration = (long) (seconds * 1000.);
|
||||
if (android.os.Build.VERSION.SDK_INT >= 26) {
|
||||
VibrationEffect ve = VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
|
||||
vibrator.vibrate(ve);
|
||||
} else {
|
||||
vibrator.vibrate(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
public boolean openURLFromLOVE(String url) {
|
||||
Log.d(TAG, "opening url = " + url);
|
||||
return openURL(url) == 0;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public boolean hasBackgroundMusic() {
|
||||
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
return audioManager.isMusicActive();
|
||||
}
|
||||
|
||||
@Keep
|
||||
public String[] buildFileTree() {
|
||||
// Map key is path, value is directory flag
|
||||
HashMap<String, Boolean> map = buildFileTree(getAssets(), "", new HashMap<>());
|
||||
ArrayList<String> result = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, Boolean> data: map.entrySet()) {
|
||||
result.add((data.getValue() ? "d" : "f") + data.getKey());
|
||||
}
|
||||
|
||||
String[] r = new String[result.size()];
|
||||
result.toArray(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public float getDPIScale() {
|
||||
DisplayMetrics metrics = getResources().getDisplayMetrics();
|
||||
return metrics.density;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public Rect getSafeArea() {
|
||||
Rect rect = null;
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= 28) {
|
||||
DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
|
||||
|
||||
if (cutout != null) {
|
||||
rect = new Rect();
|
||||
rect.set(
|
||||
cutout.getSafeInsetLeft(),
|
||||
cutout.getSafeInsetTop(),
|
||||
cutout.getSafeInsetRight(),
|
||||
cutout.getSafeInsetBottom()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public String getCRequirePath() {
|
||||
ApplicationInfo applicationInfo = getApplicationInfo();
|
||||
|
||||
if (isNativeLibsExtracted()) {
|
||||
return applicationInfo.nativeLibraryDir + "/?.so";
|
||||
} else {
|
||||
// The native libs are inside the APK and can be loaded directly.
|
||||
// FIXME: What about split APKs?
|
||||
String abi = android.os.Build.SUPPORTED_ABIS[0];
|
||||
return applicationInfo.sourceDir + "!/lib/" + abi + "/?.so";
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void setImmersiveMode(boolean enable) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 28) {
|
||||
WindowManager.LayoutParams attr = getWindow().getAttributes();
|
||||
|
||||
if (enable) {
|
||||
attr.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
} else {
|
||||
attr.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
|
||||
}
|
||||
}
|
||||
|
||||
shortEdgesMode = enable;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public boolean getImmersiveMode() {
|
||||
return shortEdgesMode;
|
||||
}
|
||||
|
||||
public int getAudioSMP() {
|
||||
int smp = 256;
|
||||
AudioManager a = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
if (a != null) {
|
||||
int b = Integer.parseInt(a.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));
|
||||
smp = b > 0 ? b : smp;
|
||||
}
|
||||
|
||||
return smp;
|
||||
}
|
||||
|
||||
public int getAudioFreq() {
|
||||
int freq = 44100;
|
||||
AudioManager a = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
if (a != null) {
|
||||
int b = Integer.parseInt(a.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
|
||||
freq = b > 0 ? b : freq;
|
||||
}
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
public boolean isNativeLibsExtracted() {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
||||
ApplicationInfo appInfo = getApplicationInfo();
|
||||
return (appInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
Uri game = intent.getData();
|
||||
if (game == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSingleton == null) {
|
||||
// Game is not running, consider setting the currentGameInfo here
|
||||
|
||||
if (hasEmbeddedGame()) {
|
||||
// Send it as dropped file later
|
||||
delayedFd = convertToFileDescriptor(game);
|
||||
} else {
|
||||
// Process to GameInfo
|
||||
processOpenGame(intent, game);
|
||||
}
|
||||
} else {
|
||||
// Game is already running. Send it as dropped file.
|
||||
int fd = convertToFileDescriptor(game);
|
||||
if (fd != -1) {
|
||||
SDLActivity.onNativeDropFile("love2d://fd/" + fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HashMap<String, Boolean> buildFileTree(AssetManager assetManager, String dir, HashMap<String, Boolean> map) {
|
||||
String strippedDir = dir.endsWith("/") ? dir.substring(0, dir.length() - 1) : dir;
|
||||
|
||||
// Try open dir
|
||||
try {
|
||||
InputStream test = assetManager.open(strippedDir);
|
||||
// It's a file
|
||||
test.close();
|
||||
map.put(strippedDir, false);
|
||||
} catch (FileNotFoundException e) {
|
||||
// It's a directory
|
||||
String[] list = null;
|
||||
|
||||
// List files
|
||||
try {
|
||||
list = assetManager.list(strippedDir);
|
||||
} catch (IOException e2) {
|
||||
Log.e(TAG, strippedDir, e2);
|
||||
}
|
||||
|
||||
// Mark as file
|
||||
map.put(dir, true);
|
||||
|
||||
// This Object comparison is intentional.
|
||||
if (strippedDir != dir) {
|
||||
map.put(strippedDir, true);
|
||||
}
|
||||
|
||||
if (list != null) {
|
||||
for (String path: list) {
|
||||
buildFileTree(assetManager, dir + path + "/", map);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, dir, e);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private int convertToFileDescriptor(Uri uri) {
|
||||
try {
|
||||
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r");
|
||||
return pfd.getFd();
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.d(TAG, "Failed attempt to convert " + uri.toString() + " to file descriptor", e);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void processOpenGame(Intent intent, Uri game) {
|
||||
String scheme = game.getScheme();
|
||||
String path = game.getPath();
|
||||
|
||||
if (scheme.equals("content")) {
|
||||
// The intent may have more information about the filename
|
||||
currentGameInfo.identity = intent.getStringExtra("name");
|
||||
|
||||
if (currentGameInfo.identity == null) {
|
||||
// Use "lovegame" as fallback
|
||||
// TODO: Use the content URI basename
|
||||
Log.w(TAG, "Using \"lovegame\" as fallback for game identity (Uri " + game + ")");
|
||||
currentGameInfo.identity = "lovegame";
|
||||
}
|
||||
|
||||
currentGameInfo.fd = convertToFileDescriptor(game);
|
||||
} else if (scheme.equals("file")) {
|
||||
File f = new File(path);
|
||||
currentGameInfo.path = path;
|
||||
currentGameInfo.identity = f.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2022 LOVE Development Team
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
package org.love2d.android;
|
||||
|
||||
public class GameInfo {
|
||||
int fd;
|
||||
String identity;
|
||||
String path;
|
||||
// TODO: Custom Filesystem class for users who develop using Android.
|
||||
// but evaluate if it's worth it
|
||||
|
||||
GameInfo() {
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
public int getFd() {
|
||||
return fd;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getIdentity() {
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.LÖVEForAndroid.NoActionBar"
|
||||
android:launchMode="singleTask" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
Reference in New Issue
Block a user