Rewrite GameActivity.java

This commit is contained in:
Miku AuahDark
2022-02-05 23:23:59 +08:00
parent 10622388b5
commit 90e7e60955
3 changed files with 435 additions and 2 deletions
@@ -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;
}
}
-1
View File
@@ -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" />