changeset 2681:c4256ce2c45a

Updated Android port using gradle toolchain and with basic Storage Access Framework support for Android 11+ support
author Michael Pavone <pavone@retrodev.com>
date Wed, 26 Mar 2025 01:20:55 -0700
parents e3394457427e
children 143cb5762ec9
files .hgignore Android.mk android/AndroidManifest.xml android/ant.properties android/app/build.gradle android/app/jni/Android.mk android/app/jni/Application.mk android/app/jni/src android/app/proguard-rules.pro android/app/src/main/AndroidManifest.xml android/app/src/main/assets/default.cfg android/app/src/main/assets/images android/app/src/main/assets/rom.db android/app/src/main/assets/shaders android/app/src/main/assets/systems.cfg android/app/src/main/java/com/retrodev/blastem/BlastEmActivity.java android/app/src/main/java/org android/app/src/main/res/mipmap-hdpi/ic_launcher.png android/app/src/main/res/mipmap-mdpi/ic_launcher.png android/app/src/main/res/mipmap-xhdpi/ic_launcher.png android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png android/app/src/main/res/values/colors.xml android/app/src/main/res/values/strings.xml android/app/src/main/res/values/styles.xml android/assets/default.cfg android/assets/images android/assets/menu.bin android/assets/rom.db android/assets/shaders android/build.gradle android/build.properties android/build.xml android/default.properties android/gradle.properties android/gradle/wrapper/gradle-wrapper.jar android/gradle/wrapper/gradle-wrapper.properties android/gradlew android/gradlew.bat android/jni/Android.mk android/jni/Application.mk android/jni/src android/proguard-project.txt android/project.properties android/res/drawable-hdpi/ic_launcher.png android/res/drawable-mdpi/ic_launcher.png android/res/drawable-xhdpi/ic_launcher.png android/res/drawable-xxhdpi/ic_launcher.png android/res/layout/main.xml android/res/values/strings.xml android/settings.gradle android/src/com/retrodev/blastem/BlastEmActivity.java android/src/org/libsdl/app/SDL.java android/src/org/libsdl/app/SDLActivity.java android/src/org/libsdl/app/SDLAudioManager.java android/src/org/libsdl/app/SDLControllerManager.java debug.c event_log.c nuklear_ui/blastem_nuklear.c paths.c paths.h system.c terminal.c util.c util.h zip.c
diffstat 66 files changed, 953 insertions(+), 2948 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Wed Mar 26 01:20:09 2025 -0700
+++ b/.hgignore	Wed Mar 26 01:20:55 2025 -0700
@@ -21,12 +21,15 @@
 ztests
 glew/*
 lib/*
-android/obj
-android/bin
-android/gen
-android/libs
+obj/*
+z80.c
+z80.h
+m68k.c
+m68k.h
+android/.gradle
+android/app/build
+android/app/jni/SDL
 android/local.properties
-android/jni/SDL
 sdl
 *.o
 *.list
--- a/Android.mk	Wed Mar 26 01:20:09 2025 -0700
+++ b/Android.mk	Wed Mar 26 01:20:55 2025 -0700
@@ -4,25 +4,28 @@
 
 LOCAL_MODULE := main
 
-SDL_PATH := android/jni/SDL
+SDL_PATH := android/app/jni/SDL
 
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
 
-LOCAL_CFLAGS += -std=gnu99 -DX86_32 -DUSE_GLES
+LOCAL_CFLAGS += -std=gnu99 -DNEW_CORE -DUSE_GLES
 
 # Add your application source files here...
-LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \
-	68kinst.c debug.c gst.c psg.c z80_to_x86.c backend.c io.c render_sdl.c \
-	tern.c backend_x86.c gdb_remote.c m68k_core.c romdb.c m68k_core_x86.c \
-	util.c wave.c blastem.c gen.c mem.c vdp.c ym2612.c config.c gen_x86.c \
-	terminal.c z80inst.c menu.c arena.c zlib/adler32.c zlib/compress.c \
-	zlib/crc32.c zlib/deflate.c zlib/gzclose.c zlib/gzlib.c zlib/gzread.c \
-	zlib/gzwrite.c zlib/infback.c zlib/inffast.c zlib/inflate.c \
-	zlib/inftrees.c zlib/trees.c zlib/uncompr.c zlib/zutil.c \
-	nuklear_ui/font_android.c nuklear_ui/blastem_nuklear.c nuklear_ui/sfnt.c \
-	ppm.c controller_info.c png.c system.c genesis.c sms.c serialize.c \
-	saves.c hash.c xband.c zip.c bindings.c jcart.c paths.c megawifi.c \
-	nor.c i2c.c sega_mapper.c realtec.c multi_game.c net.c
+LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c 68kinst.c \
+	debug.c gst.c psg.c z80.c backend.c io.c render_sdl.c tern.c gdb_remote.c \
+	m68k.c romdb.c util.c wave.c flac.c blastem.c gen.c mem.c vdp.c ym2612.c \
+	ymf262.c ym_common.c vgm.c event_log.c render_audio.c config.c terminal.c \
+	z80inst.c menu.c arena.c zlib/adler32.c zlib/compress.c zlib/crc32.c \
+	zlib/deflate.c zlib/gzclose.c zlib/gzlib.c zlib/gzread.c zlib/gzwrite.c \
+	zlib/infback.c zlib/inffast.c zlib/inflate.c zlib/inftrees.c zlib/trees.c \
+	zlib/uncompr.c zlib/zutil.c nuklear_ui/font_android.c \
+	nuklear_ui/filechooser_null.c nuklear_ui/blastem_nuklear.c \
+	nuklear_ui/sfnt.c ppm.c controller_info.c png.c system.c genesis.c sms.c \
+	serialize.c saves.c hash.c xband.c zip.c bindings.c jcart.c paths.c \
+	megawifi.c nor.c i2c.c sega_mapper.c realtec.c multi_game.c net.c coleco.c \
+	pico_pcm.c ymz263b.c segacd.c lc8951.c cdimage.c cdd_mcu.c cd_graphics.c \
+	cdd_fader.c rf5c164.c sft_mapper.c mediaplayer.c oscilloscope.c disasm.c \
+	i8255.c gen_player.c 
 
 LOCAL_SHARED_LIBRARIES := SDL2
 
--- a/android/AndroidManifest.xml	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Replace org.libsdl.app with the identifier of your game below, e.g.
-     com.gamemaker.game
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="com.retrodev.blastem"
-      android:versionCode="1"
-      android:versionName="1.0"
-      android:installLocation="auto">
-
-    <!-- Create a Java class extending SDLActivity and place it in a
-         directory under src matching the package, e.g.
-         	src/com/gamemaker/game/MyGame.java
-
-         then replace "SDLActivity" with the name of your class (e.g. "MyGame")
-         in the XML below.
-
-         An example Java class can be found in README-android.txt
-    -->
-    <application android:label="@string/app_name"
-                 android:icon="@drawable/ic_launcher"
-				 android:banner="@drawable/ic_launcher"
-                 android:allowBackup="true"
-                 android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
-                 android:hardwareAccelerated="true"
-				 android:isGame="true"
-				 android:debuggable="true">
-        <activity android:name="BlastEmActivity"
-                  android:label="@string/app_name"
-                  android:configChanges="keyboardHidden|orientation"
-                  >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-			<intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-
-    <!-- Android 4.1.1 -->
-    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="23" />
-
-    <!-- OpenGL ES 2.0 -->
-    <uses-feature android:glEsVersion="0x00020000" />
-	
-	<uses-feature android:name="android.hardware.gamepad" android:required="false" />
-	<uses-feature android:name="android.software.leanback" android:required="false" />
-	<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
-
-    <!-- Allow writing to external storage -->
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> 
-</manifest> 
--- a/android/ant.properties	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# This file is used to override default values used by the Ant build system.
-#
-# This file must be checked into Version Control Systems, as it is
-# integral to the build system of your project.
-
-# This file is only used by the Ant script.
-
-# You can use this to override default values such as
-#  'source.dir' for the location of your java source folder and
-#  'out.dir' for the location of your output folder.
-
-# You can also use it define how the release builds are signed by declaring
-# the following properties:
-#  'key.store' for the location of your keystore and
-#  'key.alias' for the name of the key to use.
-# The password will be asked during the build when you use the 'release' target.
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/build.gradle	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,75 @@
+def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY');
+def buildAsApplication = !buildAsLibrary
+if (buildAsApplication) {
+    apply plugin: 'com.android.application'
+}
+else {
+    apply plugin: 'com.android.library'
+}
+
+android {
+    if (buildAsApplication) {
+        namespace "com.retrodev.blastem"
+    }
+    compileSdkVersion 35
+    defaultConfig {
+        minSdkVersion 19
+        targetSdkVersion 35
+        versionCode 1
+        versionName "1.0"
+        externalNativeBuild {
+            ndkBuild {
+                arguments "APP_PLATFORM=android-19"
+                abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
+            }
+            // cmake {
+            //     arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
+            //     // abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
+            //     abiFilters 'arm64-v8a'
+            // }
+        }
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    applicationVariants.all { variant ->
+        tasks["merge${variant.name.capitalize()}Assets"]
+            .dependsOn("externalNativeBuild${variant.name.capitalize()}")
+    }
+    if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {
+        sourceSets.main {
+            jniLibs.srcDir 'libs'
+        }
+        externalNativeBuild {
+            ndkBuild {
+                path 'jni/Android.mk'
+            }
+            // cmake {
+            //     path 'jni/CMakeLists.txt'
+            // }
+        }
+       
+    }
+    lint {
+        abortOnError false
+    }
+
+    if (buildAsLibrary) {
+        libraryVariants.all { variant ->
+            variant.outputs.each { output ->
+                def outputFile = output.outputFile
+                if (outputFile != null && outputFile.name.endsWith(".aar")) {
+                    def fileName = "org.libsdl.app.aar";
+                    output.outputFile = new File(outputFile.parent, fileName);
+                }
+            }
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(include: ['*.jar'], dir: 'libs')
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/jni/Android.mk	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,1 @@
+include $(call all-subdir-makefiles)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/jni/Application.mk	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,10 @@
+
+# Uncomment this if you're using STL in your project
+# You can find more information here:
+# https://developer.android.com/ndk/guides/cpp-support
+# APP_STL := c++_shared
+
+APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
+
+# Min runtime API level
+APP_PLATFORM=android-19
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/jni/src	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,1 @@
+../../..
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/proguard-rules.pro	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,98 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in [sdk]/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection {
+    void nativeCommitText(java.lang.String, int);
+    void nativeGenerateScancodeForUnichar(char);
+}
+
+-keep,includedescriptorclasses class org.libsdl.app.SDLActivity {
+    # for some reason these aren't compatible with allowoptimization modifier
+    boolean supportsRelativeMouse();
+    void setWindowStyle(boolean);
+}
+
+-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {
+    java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it
+    boolean onNativeSoftReturnKey();
+    void onNativeKeyboardFocusLost();
+    boolean isScreenKeyboardShown();
+    android.util.DisplayMetrics getDisplayDPI();
+    java.lang.String clipboardGetText();
+    boolean clipboardHasText();
+    void clipboardSetText(java.lang.String);
+    int createCustomCursor(int[], int, int, int, int);
+    void destroyCustomCursor(int);
+    android.content.Context getContext();
+    boolean getManifestEnvironmentVariables();
+    android.view.Surface getNativeSurface();
+    void initTouch();
+    boolean isAndroidTV();
+    boolean isChromebook();
+    boolean isDeXMode();
+    boolean isTablet();
+    void manualBackButton();
+    int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]);
+    void minimizeWindow();
+    int openURL(java.lang.String);
+    void requestPermission(java.lang.String, int);
+    int showToast(java.lang.String, int, int, int, int);
+    boolean sendMessage(int, int);
+    boolean setActivityTitle(java.lang.String);
+    boolean setCustomCursor(int);
+    void setOrientation(int, int, boolean, java.lang.String);
+    boolean setRelativeMouseEnabled(boolean);
+    boolean setSystemCursor(int);
+    boolean shouldMinimizeOnFocusLoss();
+    boolean showTextInput(int, int, int, int);
+}
+
+-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager {
+    boolean initialize(boolean, boolean);
+    boolean openDevice(int);
+    int sendOutputReport(int, byte[]);
+    int sendFeatureReport(int, byte[]);
+    boolean getFeatureReport(int, byte[]);
+    void closeDevice(int);
+}
+
+-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager {
+    int[] getAudioOutputDevices();
+    int[] getAudioInputDevices();
+    int[] audioOpen(int, int, int, int, int);
+    void audioWriteFloatBuffer(float[]);
+    void audioWriteShortBuffer(short[]);
+    void audioWriteByteBuffer(byte[]);
+    void audioClose();
+    int[] captureOpen(int, int, int, int, int);
+    int captureReadFloatBuffer(float[], boolean);
+    int captureReadShortBuffer(short[], boolean);
+    int captureReadByteBuffer(byte[], boolean);
+    void captureClose();
+    void audioSetThreadPriority(boolean, int);
+    native int nativeSetupJNI();
+    native void removeAudioDevice(boolean, int);
+    native void addAudioDevice(boolean, int);
+}
+
+-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {
+    void pollInputDevices();
+    void pollHapticDevices();
+    void hapticRun(int, float, int);
+    void hapticStop(int);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/AndroidManifest.xml	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Replace com.test.game with the identifier of your game below, e.g.
+     com.gamemaker.game
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.retrodev.blastem"
+    android:versionCode="1"
+    android:versionName="1.0"
+    android:installLocation="auto">
+
+    <!-- OpenGL ES 2.0 -->
+    <uses-feature android:glEsVersion="0x00020000" />
+
+    <!-- Touchscreen support -->
+    <uses-feature
+        android:name="android.hardware.touchscreen"
+        android:required="false" />
+
+    <!-- Game controller support -->
+    <uses-feature
+        android:name="android.hardware.bluetooth"
+        android:required="false" />
+    <uses-feature
+        android:name="android.hardware.gamepad"
+        android:required="false" />
+    <uses-feature
+        android:name="android.hardware.usb.host"
+        android:required="false" />
+
+    <!-- External mouse input events -->
+    <uses-feature
+        android:name="android.hardware.type.pc"
+        android:required="false" />
+
+    <!-- Audio recording support -->
+    <!-- if you want to capture audio, uncomment this. -->
+    <!-- <uses-feature
+        android:name="android.hardware.microphone"
+        android:required="false" /> -->
+
+    <!-- Allow downloading to the external storage on Android 5.1 and older -->
+    <!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
+
+    <!-- Allow access to Bluetooth devices -->
+    <!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
+    <!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> -->
+    <!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> -->
+
+    <!-- Allow access to the vibrator -->
+    <uses-permission android:name="android.permission.VIBRATE" />
+
+    <!-- if you want to capture audio, uncomment this. -->
+    <!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->
+	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <!-- Create a Java class extending SDLActivity and place it in a
+         directory under app/src/main/java matching the package, e.g. app/src/main/java/com/gamemaker/game/MyGame.java
+ 
+         then replace "SDLActivity" with the name of your class (e.g. "MyGame")
+         in the XML below.
+
+         An example Java class can be found in README-android.md
+    -->
+    <application android:label="@string/app_name"
+        android:icon="@mipmap/ic_launcher"
+        android:allowBackup="true"
+        android:theme="@style/AppTheme"
+        android:hardwareAccelerated="true" >
+
+        <!-- Example of setting SDL hints from AndroidManifest.xml:
+        <meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
+         -->
+     
+        <activity android:name="BlastEmActivity"
+            android:label="@string/app_name"
+            android:alwaysRetainTaskState="true"
+            android:launchMode="singleInstance"
+            android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
+            android:preferMinimalPostProcessing="true"
+            android:exported="true"
+            >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <!-- Let Android know that we can handle some USB devices and should receive this event -->
+            <intent-filter>
+                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+            </intent-filter>
+            <!-- Drop file event -->
+            <!--
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+            -->
+        </activity>
+    </application>
+
+</manifest>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/assets/default.cfg	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,1 @@
+../../../../../default.cfg
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/assets/images	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,1 @@
+../../../../../images
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/assets/rom.db	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,1 @@
+../../../../../rom.db
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/assets/shaders	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,1 @@
+../../../../../shaders
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/assets/systems.cfg	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,1 @@
+../../../../../systems.cfg
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/java/com/retrodev/blastem/BlastEmActivity.java	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,148 @@
+package com.retrodev.blastem;
+import org.libsdl.app.SDLActivity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
+import android.util.Log;
+import android.view.View;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+
+
+public class BlastEmActivity extends SDLActivity
+{
+	static final int DOC_TREE_CODE = 4242;
+	boolean chooseDirInProgress = false;
+	String chooseDirResult = null;
+	Map<String, Uri> uriMap = new HashMap<String, Uri>();
+	@Override
+    protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		
+		//set immersive mode on devices that support it
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+			View blah = mSurface;
+			blah.setSystemUiVisibility(
+				View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+			);
+		}
+	}
+	
+	public String getRomPath() {
+		if (chooseDirInProgress) {
+			if (chooseDirResult != null) {
+				chooseDirInProgress = false;
+				return chooseDirResult;
+			}
+			return null;
+		}
+		String extStorage = Environment.getExternalStorageDirectory().getAbsolutePath();
+		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+			return extStorage;
+		}
+		chooseDirInProgress = true;
+		Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+		//intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse(extStorage));
+		/*intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
+		intent.putExtra("android.content.extra.FANCY", true);
+		intent.putExtra("android.content.extra.SHOW_FILESIZE", true);*/
+		startActivityForResult(intent, DOC_TREE_CODE);
+		return null;
+	}
+	
+	public String[] readUriDir(String uriString) {
+		Uri uri = uriMap.get(uriString);
+		if (uri == null) {
+			return new String[0];
+		}
+		//adapted from some androidx.documentfile
+		final ContentResolver resolver = getContentResolver();
+        final ArrayList<String> results = new ArrayList<>();
+
+        Cursor c = null;
+        try {
+			Log.i("BlastEm", "getTreeDocumentId: " + DocumentsContract.getTreeDocumentId(uri));
+			final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri,
+                DocumentsContract.getTreeDocumentId(uri)
+			);
+            c = resolver.query(
+				childrenUri, new String[] {
+					DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+					DocumentsContract.Document.COLUMN_DISPLAY_NAME,
+					DocumentsContract.Document.COLUMN_MIME_TYPE
+				}, null, null, null
+			);
+            while (c.moveToNext()) {
+                final String documentId = c.getString(0);
+				String name = c.getString(1);
+				final String mime = c.getString(2);
+                final Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(uri,
+                        documentId);
+                uriMap.put(uriString + "/" + name, documentUri);
+				if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mime)) {
+					name += "/";
+				}
+				results.add(name);
+            }
+        } catch (Exception e) {
+            Log.w("BlastEm", "Failed query: " + e);
+        } finally {
+            if (c != null) {
+				c.close();
+			}
+        }
+		return results.toArray(new String[0]);
+	}
+	
+	public int openUriAsFd(String uriString, String mode) {
+		Uri uri = uriMap.get(uriString);
+		if (uri == null) {
+			Log.w("BlastEm", "Did not find URI in map: " + uriString);
+			return 0;
+		}
+		if (mode.equals("rb")) {
+			mode = "r";
+		} else if (mode.equals("wb")) {
+			mode = "w";
+		}
+		try {
+			ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, mode);
+			if (pfd != null) {
+				return pfd.detachFd();
+			}
+			Log.w("BlastEm", "openFileDescriptor returned null: " + uriString);
+		} catch (FileNotFoundException e) {
+			Log.w("BlastEm", "Failed to open URI: " + e);
+		} catch (IllegalArgumentException e) {
+			Log.w("BlastEm", "Failed to open URI: " + e);
+		}
+		return 0;
+	}
+	
+	@Override
+	public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
+		if (requestCode == DOC_TREE_CODE) {
+			if (resultCode == RESULT_OK && resultData != null) {
+				Uri uri = resultData.getData();
+				getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+				chooseDirResult = uri.toString();
+				uriMap.put(chooseDirResult, uri);
+				Log.i("BlastEm", "ACTION_OPEN_DOCUMENT_TREE got URI " + chooseDirResult);
+			} else {
+				Log.i("BlastEm", "ACTION_OPEN_DOCUMENT_TREE failed! ");
+				chooseDirResult = "";
+			}
+		} else {
+			super.onActivityResult(requestCode, resultCode, resultData);
+		}
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/java/org	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,1 @@
+../../../jni/SDL/android-project/app/src/main/java/org
\ No newline at end of file
Binary file android/app/src/main/res/mipmap-hdpi/ic_launcher.png has changed
Binary file android/app/src/main/res/mipmap-mdpi/ic_launcher.png has changed
Binary file android/app/src/main/res/mipmap-xhdpi/ic_launcher.png has changed
Binary file android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png has changed
Binary file android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/res/values/colors.xml	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/res/values/strings.xml	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">BlastEm</string>
+</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/app/src/main/res/values/styles.xml	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="android:Theme.NoTitleBar.Fullscreen">
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
--- a/android/assets/default.cfg	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,395 +0,0 @@
-
-bindings {
-	keys {
-		up gamepads.1.up
-		down gamepads.1.down
-		left gamepads.1.left
-		right gamepads.1.right
-		a gamepads.1.a
-		s gamepads.1.b
-		d gamepads.1.c
-		q gamepads.1.x
-		w gamepads.1.y
-		e gamepads.1.z
-		f gamepads.1.mode
-		enter gamepads.1.start
-
-		r ui.release_mouse
-		[ ui.vdp_debug_mode
-		u ui.enter_debugger
-		p ui.screenshot
-		b ui.plane_debug
-		v ui.vram_debug
-		c ui.cram_debug
-		n ui.compositing_debug
-		esc ui.exit
-		` ui.save_state
-		0 ui.set_speed.0
-		1 ui.set_speed.1
-		2 ui.set_speed.2
-		3 ui.set_speed.3
-		4 ui.set_speed.4
-		5 ui.set_speed.5
-		6 ui.set_speed.6
-		7 ui.set_speed.7
-		= ui.next_speed
-		- ui.prev_speed
-		f11 ui.toggle_fullscreen
-		tab ui.soft_reset
-		f5 ui.reload
-		z ui.sms_pause
-		rctrl ui.toggle_keyboard_captured
-		
-		
-		select gamepads.1.c
-		play gamepads.1.start
-		back ui.exit
-	}
-	pads {
-		default {
-			dpads {
-				0 {
-					up gamepads.n.up
-					down gamepads.n.down
-					left gamepads.n.left
-					right gamepads.n.right
-				}
-			}
-			buttons {
-				a gamepads.n.a
-				b gamepads.n.b
-				rightshoulder gamepads.n.c
-				x gamepads.n.x
-				y gamepads.n.y
-				leftshoulder gamepads.n.z
-				back gamepads.n.mode
-				start gamepads.n.start
-				guide ui.exit
-				leftstick ui.save_state
-			}
-			axes {
-				lefty.positive gamepads.n.down
-				lefty.negative gamepads.n.up
-				leftx.positive gamepads.n.right
-				leftx.negative gamepads.n.left
-				lefttrigger ui.prev_speed
-				righttrigger ui.next_speed
-			}
-		}
-		ps4_6b_right {
-			axes {
-				lefttrigger ui.next_speed
-				leftx.negative gamepads.n.up
-				leftx.positive gamepads.n.down
-				lefty.negative gamepads.n.left
-				lefty.positive gamepads.n.right
-				righttrigger gamepads.n.c
-			}
-			buttons {
-				a gamepads.n.a
-				b gamepads.n.b
-				back ui.sms_pause
-				guide ui.exit
-				leftshoulder gamepads.n.mode
-				leftstick ui.save_state
-				rightshoulder gamepads.n.z
-				rightstick ui.prev_speed
-				start gamepads.n.start
-				x gamepads.n.x
-				y gamepads.n.y
-			}
-			dpads {
-				0 {
-					down gamepads.n.down
-					left gamepads.n.left
-					right gamepads.n.right
-					up gamepads.n.up
-				}
-			}
-		}
-		ps3_6b_right {
-			axes {
-				lefttrigger ui.next_speed
-				leftx.negative gamepads.n.up
-				leftx.positive gamepads.n.down
-				lefty.negative gamepads.n.left
-				lefty.positive gamepads.n.right
-				righttrigger gamepads.n.c
-			}
-			buttons {
-				a gamepads.n.a
-				b gamepads.n.b
-				back ui.sms_pause
-				guide ui.exit
-				leftshoulder gamepads.n.mode
-				leftstick ui.save_state
-				rightshoulder gamepads.n.z
-				rightstick ui.prev_speed
-				start gamepads.n.start
-				x gamepads.n.x
-				y gamepads.n.y
-			}
-			dpads {
-				0 {
-					down gamepads.n.down
-					left gamepads.n.left
-					right gamepads.n.right
-					up gamepads.n.up
-				}
-			}
-		}
-		xbox_360_6b_right {
-			axes {
-				lefttrigger ui.next_speed
-				leftx.negative gamepads.n.up
-				leftx.positive gamepads.n.down
-				lefty.negative gamepads.n.left
-				lefty.positive gamepads.n.right
-				righttrigger gamepads.n.c
-			}
-			buttons {
-				a gamepads.n.a
-				b gamepads.n.b
-				back ui.sms_pause
-				guide ui.exit
-				leftshoulder gamepads.n.mode
-				leftstick ui.save_state
-				rightshoulder gamepads.n.z
-				rightstick ui.prev_speed
-				start gamepads.n.start
-				x gamepads.n.x
-				y gamepads.n.y
-			}
-			dpads {
-				0 {
-					down gamepads.n.down
-					left gamepads.n.left
-					right gamepads.n.right
-					up gamepads.n.up
-				}
-			}
-		}
-		xbone_6b_right {
-			axes {
-				lefttrigger ui.next_speed
-				leftx.negative gamepads.n.up
-				leftx.positive gamepads.n.down
-				lefty.negative gamepads.n.left
-				lefty.positive gamepads.n.right
-				righttrigger gamepads.n.c
-			}
-			buttons {
-				a gamepads.n.a
-				b gamepads.n.b
-				back ui.sms_pause
-				guide ui.exit
-				leftshoulder gamepads.n.mode
-				leftstick ui.save_state
-				rightshoulder gamepads.n.z
-				rightstick ui.prev_speed
-				start gamepads.n.start
-				x gamepads.n.x
-				y gamepads.n.y
-			}
-			dpads {
-				0 {
-					down gamepads.n.down
-					left gamepads.n.left
-					right gamepads.n.right
-					up gamepads.n.up
-				}
-			}
-		}
-		genesis_6b_bumpers {
-			axes {
-				lefttrigger ui.exit
-				righttrigger gamepads.n.mode
-			}
-			buttons {
-				a gamepads.n.a
-				b gamepads.n.b
-				back ui.sms_pause
-				guide ui.exit
-				leftshoulder gamepads.n.z
-				rightshoulder gamepads.n.c
-				start gamepads.n.start
-				x gamepads.n.x
-				y gamepads.n.y
-			}
-			dpads {
-				0 {
-					down gamepads.n.down
-					left gamepads.n.left
-					right gamepads.n.right
-					up gamepads.n.up
-				}
-			}
-		}
-		saturn_6b_bumpers {
-			axes {
-				lefttrigger ui.exit
-				righttrigger gamepads.n.mode
-			}
-			buttons {
-				a gamepads.n.a
-				b gamepads.n.b
-				back ui.sms_pause
-				guide ui.exit
-				leftshoulder gamepads.n.z
-				rightshoulder gamepads.n.c
-				start gamepads.n.start
-				x gamepads.n.x
-				y gamepads.n.y
-			}
-			dpads {
-				0 {
-					down gamepads.n.down
-					left gamepads.n.left
-					right gamepads.n.right
-					up gamepads.n.up
-				}
-			}
-		}
-	}
-	mice {
-		0 {
-			motion mouse.1.motion
-			buttons {
-				1 mouse.1.left
-				2 mouse.1.middle
-				3 mouse.1.right
-				4 mouse.1.start
-			}
-		}
-		#having the second host mouse also mapped to the first emulated
-		#mouse is useful for laptop users with an external mouse
-		1 {
-			motion mouse.1.motion
-			buttons {
-				1 mouse.1.left
-				2 mouse.1.middle
-				3 mouse.1.right
-				4 mouse.1.start
-			}
-		}
-	}
-}
-
-io {
-	devices {
-		1 gamepad6.1
-		2 gamepad6.2
-	}
-}
-
-video {
-	#special value "stretch" will cause aspect to match window aspect ratio
-	aspect 4:3
-	width 640
-	#height is normally calculated automatically from width using the aspect setting
-	#if you would like to set it explicitly, uncomment the line below
-	#height 480
-	vertex_shader default.v.glsl
-	fragment_shader default.f.glsl
-	scanlines off
-	vsync off
-	fullscreen off
-	#setting gl to off, will force use of the SDL2 fallback renderer
-	#this is useful for those running on machines with Open GL 2.0 unavailable
-	#so the warning doesn't display on startup
-	gl on
-	#scaling can be linear (for linear interpolation) or nearest (for nearest neighbor)
-	scaling linear
-	ntsc {
-		overscan {
-			#these values will result in square pixels in H40 mode
-			top 2
-			bottom 1
-			#if you want to completely hide the border instead
-			#comment out those two lines and uncomment these
-			#top 11
-			#bottom 8
-			
-			#these values will completely hide the horizontal border
-			left 13
-			right 14
-		}
-	}
-	pal {
-		overscan {
-			#these values will produce the same size border in V30 mode
-			#as the default NTSC settings will produce in V24 mode
-			#this results in a slightly vertically squished picture
-			#which is probably approximately correct on a properly calibrated TV
-			top 21
-			bottom 17
-			#for square pixels and zero border in V30 mode
-			#coment out those two lines and uncomment these
-			#top 30
-			#bottom 24
-			
-			#these values will completely hide the horizontal border
-			left 13
-			right 14
-		}
-	}
-}
-
-audio {
-	rate 48000
-	buffer 512
-	lowpass_cutoff 3390
-}
-
-clocks {
-	m68k_divider 7
-	max_cycles 3420
-	speeds {
-		0 100
-		1 150
-		2 200
-		3 300
-		4 400
-		5 25
-		6 50
-		7 75
-	}
-}
-
-ui {
-	#specifies the ROM that implements the Menu UI
-	rom menu.bin
-	#starting path for ROM browsing, accepts special variables $HOME, $EXEDIR
-	#and variables defined in the OS environment
-	initial_path $HOME
-	#if this is set to on, then the menu will remember the last path when visited
-	#if it's set to off, initial_path will always be used on startup
-	remember_path on
-	#path for storing internal screenshots, accepts the same variables as initial_path
-	screenshot_path $HOME
-	#see strftime for the format specifiers valid in screenshot_template
-	screenshot_template blastem_%Y%m%d_%H%M%S.png
-	#path template for saving SRAM, EEPROM and savestates
-	#accepts special variables $HOME, $EXEDIR, $USERDATA, $ROMNAME
-	save_path $USERDATA/blastem/$ROMNAME
-	#space delimited list of file extensions to filter against in menu
-	extensions bin gen md smd sms gg zip gz
-	#specifies the preferred save-state format, set to gst for Genecyst compatible states
-	state_format native
-}
-
-system {
-	#controls how the emulated system is synced to the host
-	#video provides the smoothest experience when the host and emulated system have similar refresh rates
-	#audio provides lower audio latency, especially when there is a refresh rate mismatch
-	sync_source audio
-	#set this to random to debug initialization bugs
-	ram_init zero
-	default_region U
-	#controls whether MegaWiFi support is enabled or not
-	#MegaWiFi allows ROMs to make connections to the internet
-	#so it should only be enabled for ROMs you trust
-	megawifi off
-}
-
-
--- a/android/assets/images	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../../images
\ No newline at end of file
--- a/android/assets/menu.bin	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../../menu.bin
\ No newline at end of file
--- a/android/assets/rom.db	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../../rom.db
\ No newline at end of file
--- a/android/assets/shaders	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../../shaders
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/build.gradle	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        mavenCentral()
+        google()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:8.1.1'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        mavenCentral()
+        google()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
--- a/android/build.properties	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# This file is used to override default values used by the Ant build system.
-# 
-# This file must be checked in Version Control Systems, as it is
-# integral to the build system of your project.
-
-# This file is only used by the Ant script.
-
-# You can use this to override default values such as
-#  'source.dir' for the location of your java source folder and
-#  'out.dir' for the location of your output folder.
-
-# You can also use it define how the release builds are signed by declaring
-# the following properties:
-#  'key.store' for the location of your keystore and
-#  'key.alias' for the name of the key to use.
-# The password will be asked during the build when you use the 'release' target.
-
--- a/android/build.xml	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This should be changed to the name of your project -->
-<project name="BlastEmActivity" default="help">
-
-    <!-- The local.properties file is created and updated by the 'android' tool.
-         It contains the path to the SDK. It should *NOT* be checked into
-         Version Control Systems. -->
-    <property file="local.properties" />
-
-    <!-- The ant.properties file can be created by you. It is only edited by the
-         'android' tool to add properties to it.
-         This is the place to change some Ant specific build properties.
-         Here are some properties you may want to change/update:
-
-         source.dir
-             The name of the source directory. Default is 'src'.
-         out.dir
-             The name of the output directory. Default is 'bin'.
-
-         For other overridable properties, look at the beginning of the rules
-         files in the SDK, at tools/ant/build.xml
-
-         Properties related to the SDK location or the project target should
-         be updated using the 'android' tool with the 'update' action.
-
-         This file is an integral part of the build system for your
-         application and should be checked into Version Control Systems.
-
-         -->
-    <property file="ant.properties" />
-
-    <!-- if sdk.dir was not set from one of the property file, then
-         get it from the ANDROID_HOME env var.
-         This must be done before we load project.properties since
-         the proguard config can use sdk.dir -->
-    <property environment="env" />
-    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
-        <isset property="env.ANDROID_HOME" />
-    </condition>
-
-    <!-- The project.properties file is created and updated by the 'android'
-         tool, as well as ADT.
-
-         This contains project specific properties such as project target, and library
-         dependencies. Lower level build properties are stored in ant.properties
-         (or in .classpath for Eclipse projects).
-
-         This file is an integral part of the build system for your
-         application and should be checked into Version Control Systems. -->
-    <loadproperties srcFile="project.properties" />
-
-    <!-- quick check on sdk.dir -->
-    <fail
-            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
-            unless="sdk.dir"
-    />
-
-    <!--
-        Import per project custom build rules if present at the root of the project.
-        This is the place to put custom intermediary targets such as:
-            -pre-build
-            -pre-compile
-            -post-compile (This is typically used for code obfuscation.
-                           Compiled code location: ${out.classes.absolute.dir}
-                           If this is not done in place, override ${out.dex.input.absolute.dir})
-            -post-package
-            -post-build
-            -pre-clean
-    -->
-    <import file="custom_rules.xml" optional="true" />
-
-    <!-- Import the actual build file.
-
-         To customize existing targets, there are two options:
-         - Customize only one target:
-             - copy/paste the target into this file, *before* the
-               <import> task.
-             - customize it to your needs.
-         - Customize the whole content of build.xml
-             - copy/paste the content of the rules files (minus the top node)
-               into this file, replacing the <import> task.
-             - customize to your needs.
-
-         ***********************
-         ****** IMPORTANT ******
-         ***********************
-         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
-         in order to avoid having your file be overridden by tools such as "android update project"
-    -->
-    <!-- version-tag: 1 -->
-    <import file="${sdk.dir}/tools/ant/build.xml" />
-
-</project>
--- a/android/default.properties	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-# 
-# This file must be checked in Version Control Systems.
-# 
-# To customize properties used by the Ant build system use,
-# "build.properties", and override values to adapt the script to your
-# project structure.
-
-# Project target.
-target=android-12
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/gradle.properties	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
Binary file android/gradle/wrapper/gradle-wrapper.jar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/gradle/wrapper/gradle-wrapper.properties	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,6 @@
+#Thu Nov 11 18:20:34 PST 2021
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/gradlew	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|grep -E -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|grep -E -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/gradlew.bat	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
--- a/android/jni/Android.mk	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-APP_ABI=x86
-include $(call all-subdir-makefiles)
--- a/android/jni/Application.mk	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-
-# Uncomment this if you're using STL in your project
-# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information
-# APP_STL := stlport_static 
-
-APP_ABI := x86
-APP_PLATFORM := android-16
-APP_OPTIM := release
--- a/android/jni/src	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../..
\ No newline at end of file
--- a/android/proguard-project.txt	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-# To enable ProGuard in your project, edit project.properties
-# to define the proguard.config property as described in that file.
-#
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in ${sdk.dir}/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the ProGuard
-# include property in project.properties.
-#
-# For more details, see
-#   http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-#   public *;
-#}
--- a/android/project.properties	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-23
Binary file android/res/drawable-hdpi/ic_launcher.png has changed
Binary file android/res/drawable-mdpi/ic_launcher.png has changed
Binary file android/res/drawable-xhdpi/ic_launcher.png has changed
Binary file android/res/drawable-xxhdpi/ic_launcher.png has changed
--- a/android/res/layout/main.xml	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    >
-<TextView  
-    android:layout_width="fill_parent" 
-    android:layout_height="wrap_content" 
-    android:text="Hello World, SDLActivity"
-    />
-</LinearLayout>
-
--- a/android/res/values/strings.xml	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <string name="app_name">BlastEm</string>
-</resources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android/settings.gradle	Wed Mar 26 01:20:55 2025 -0700
@@ -0,0 +1,1 @@
+include ':app'
--- a/android/src/com/retrodev/blastem/BlastEmActivity.java	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-package com.retrodev.blastem;
-import org.libsdl.app.SDLActivity;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.View;
-
-
-public class BlastEmActivity extends SDLActivity
-{
-	@Override
-    protected void onCreate(Bundle savedInstanceState) {
-		super.onCreate(savedInstanceState);
-		
-		//set immersive mode on devices that support it
-		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
-			View blah = mSurface;
-			blah.setSystemUiVisibility(
-				View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
-			);
-		}
-	}
-}
\ No newline at end of file
--- a/android/src/org/libsdl/app/SDL.java	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-package org.libsdl.app;
-
-import android.content.Context;
-
-/**
-    SDL library initialization
-*/
-public class SDL {
-
-    // This function should be called first and sets up the native code
-    // so it can call into the Java classes
-    public static void setupJNI() {
-        SDLActivity.nativeSetupJNI();
-        SDLAudioManager.nativeSetupJNI();
-        SDLControllerManager.nativeSetupJNI();
-    }
-
-    // This function should be called each time the activity is started
-    public static void initialize() {
-        setContext(null);
-
-        SDLActivity.initialize();
-        SDLAudioManager.initialize();
-        SDLControllerManager.initialize();
-    }
-
-    // This function stores the current activity (SDL or not)
-    public static void setContext(Context context) {
-        mContext = context;
-    }
-
-    public static Context getContext() {
-        return mContext;
-    }
-
-    protected static Context mContext;
-}
--- a/android/src/org/libsdl/app/SDLActivity.java	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1595 +0,0 @@
-package org.libsdl.app;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.lang.reflect.Method;
-import java.util.Objects;
-
-import android.app.*;
-import android.content.*;
-import android.text.InputType;
-import android.view.*;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.RelativeLayout;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.os.*;
-import android.util.Log;
-import android.util.SparseArray;
-import android.graphics.*;
-import android.graphics.drawable.Drawable;
-import android.hardware.*;
-import android.content.pm.ActivityInfo;
-
-/**
-    SDL Activity
-*/
-public class SDLActivity extends Activity {
-    private static final String TAG = "SDL";
-
-    public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus;
-
-    // Handle the state of the native layer
-    public enum NativeState {
-           INIT, RESUMED, PAUSED 
-    }
-
-    public static NativeState mNextNativeState;
-    public static NativeState mCurrentNativeState;
-
-    public static boolean mExitCalledFromJava;
-
-    /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
-    public static boolean mBrokenLibraries;
-
-    // If we want to separate mouse and touch events.
-    //  This is only toggled in native code when a hint is set!
-    public static boolean mSeparateMouseAndTouch;
-
-    // Main components
-    protected static SDLActivity mSingleton;
-    protected static SDLSurface mSurface;
-    protected static View mTextEdit;
-    protected static boolean mScreenKeyboardShown;
-    protected static ViewGroup mLayout;
-    protected static SDLClipboardHandler mClipboardHandler;
-
-
-    // This is what SDL runs in. It invokes SDL_main(), eventually
-    protected static Thread mSDLThread;
-
-    /**
-     * This method returns the name of the shared object with the application entry point
-     * It can be overridden by derived classes.
-     */
-    protected String getMainSharedObject() {
-        String library;
-        String[] libraries = SDLActivity.mSingleton.getLibraries();
-        if (libraries.length > 0) {
-            library = "lib" + libraries[libraries.length - 1] + ".so";
-        } else {
-            library = "libmain.so";
-        }
-        return library;
-    }
-
-    /**
-     * This method returns the name of the application entry point
-     * It can be overridden by derived classes.
-     */
-    protected String getMainFunction() {
-        return "SDL_main";
-    }
-
-    /**
-     * This method is called by SDL before loading the native shared libraries.
-     * It can be overridden to provide names of shared libraries to be loaded.
-     * The default implementation returns the defaults. It never returns null.
-     * An array returned by a new implementation must at least contain "SDL2".
-     * Also keep in mind that the order the libraries are loaded may matter.
-     * @return names of shared libraries to be loaded (e.g. "SDL2", "main").
-     */
-    protected String[] getLibraries() {
-        return new String[] {
-            "SDL2",
-            // "SDL2_image",
-            // "SDL2_mixer",
-            // "SDL2_net",
-            // "SDL2_ttf",
-            "main"
-        };
-    }
-
-    // Load the .so
-    public void loadLibraries() {
-       for (String lib : getLibraries()) {
-          System.loadLibrary(lib);
-       }
-    }
-
-    /**
-     * This method is called by SDL before starting the native application thread.
-     * It can be overridden to provide the arguments after the application name.
-     * The default implementation returns an empty array. It never returns null.
-     * @return arguments for the native application.
-     */
-    protected String[] getArguments() {
-        return new String[0];
-    }
-
-    public static void initialize() {
-        // The static nature of the singleton and Android quirkyness force us to initialize everything here
-        // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
-        mSingleton = null;
-        mSurface = null;
-        mTextEdit = null;
-        mLayout = null;
-        mClipboardHandler = null;
-        mSDLThread = null;
-        mExitCalledFromJava = false;
-        mBrokenLibraries = false;
-        mIsResumedCalled = false;
-        mIsSurfaceReady = false;
-        mHasFocus = true;
-        mNextNativeState = NativeState.INIT;
-        mCurrentNativeState = NativeState.INIT;
-    }
-
-    // Setup
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        Log.v(TAG, "Device: " + android.os.Build.DEVICE);
-        Log.v(TAG, "Model: " + android.os.Build.MODEL);
-        Log.v(TAG, "onCreate()");
-        super.onCreate(savedInstanceState);
-
-        // Load shared libraries
-        String errorMsgBrokenLib = "";
-        try {
-            loadLibraries();
-        } catch(UnsatisfiedLinkError e) {
-            System.err.println(e.getMessage());
-            mBrokenLibraries = true;
-            errorMsgBrokenLib = e.getMessage();
-        } catch(Exception e) {
-            System.err.println(e.getMessage());
-            mBrokenLibraries = true;
-            errorMsgBrokenLib = e.getMessage();
-        }
-
-        if (mBrokenLibraries)
-        {
-            AlertDialog.Builder dlgAlert  = new AlertDialog.Builder(this);
-            dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall."
-                  + System.getProperty("line.separator")
-                  + System.getProperty("line.separator")
-                  + "Error: " + errorMsgBrokenLib);
-            dlgAlert.setTitle("SDL Error");
-            dlgAlert.setPositiveButton("Exit",
-                new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog,int id) {
-                        // if this button is clicked, close current activity
-                        SDLActivity.mSingleton.finish();
-                    }
-                });
-           dlgAlert.setCancelable(false);
-           dlgAlert.create().show();
-
-           return;
-        }
-
-        // Set up JNI
-        SDL.setupJNI();
-
-        // Initialize state
-        SDL.initialize();
-
-        // So we can call stuff from static callbacks
-        mSingleton = this;
-        SDL.setContext(this);
-
-        if (Build.VERSION.SDK_INT >= 11) {
-            mClipboardHandler = new SDLClipboardHandler_API11();
-        } else {
-            /* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */
-            mClipboardHandler = new SDLClipboardHandler_Old();
-        }
-
-        // Set up the surface
-        mSurface = new SDLSurface(getApplication());
-
-        mLayout = new RelativeLayout(this);
-        mLayout.addView(mSurface);
-
-        setContentView(mLayout);
-        
-        // Get filename from "Open with" of another application
-        Intent intent = getIntent();
-        if (intent != null && intent.getData() != null) {
-            String filename = intent.getData().getPath();
-            if (filename != null) {
-                Log.v(TAG, "Got filename: " + filename);
-                SDLActivity.onNativeDropFile(filename);
-            }
-        }
-    }
-
-    // Events
-    @Override
-    protected void onPause() {
-        Log.v(TAG, "onPause()");
-        super.onPause();
-        mNextNativeState = NativeState.PAUSED;
-        mIsResumedCalled = false;
-
-        if (SDLActivity.mBrokenLibraries) {
-           return;
-        }
-
-        SDLActivity.handleNativeState();
-    }
-
-    @Override
-    protected void onResume() {
-        Log.v(TAG, "onResume()");
-        super.onResume();
-        mNextNativeState = NativeState.RESUMED;
-        mIsResumedCalled = true;
-
-        if (SDLActivity.mBrokenLibraries) {
-           return;
-        }
-
-        SDLActivity.handleNativeState();
-    }
-
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        super.onWindowFocusChanged(hasFocus);
-        Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
-
-        if (SDLActivity.mBrokenLibraries) {
-           return;
-        }
-
-        SDLActivity.mHasFocus = hasFocus;
-        if (hasFocus) {
-           mNextNativeState = NativeState.RESUMED;
-        } else {
-           mNextNativeState = NativeState.PAUSED;
-        }
-        
-        SDLActivity.handleNativeState();
-    }
-
-    @Override
-    public void onLowMemory() {
-        Log.v(TAG, "onLowMemory()");
-        super.onLowMemory();
-
-        if (SDLActivity.mBrokenLibraries) {
-           return;
-        }
-
-        SDLActivity.nativeLowMemory();
-    }
-
-    @Override
-    protected void onDestroy() {
-        Log.v(TAG, "onDestroy()");
-
-        if (SDLActivity.mBrokenLibraries) {
-           super.onDestroy();
-           // Reset everything in case the user re opens the app
-           SDLActivity.initialize();
-           return;
-        }
-
-        mNextNativeState = NativeState.PAUSED;
-        SDLActivity.handleNativeState();
-
-        // Send a quit message to the application
-        SDLActivity.mExitCalledFromJava = true;
-        SDLActivity.nativeQuit();
-
-        // Now wait for the SDL thread to quit
-        if (SDLActivity.mSDLThread != null) {
-            try {
-                SDLActivity.mSDLThread.join();
-            } catch(Exception e) {
-                Log.v(TAG, "Problem stopping thread: " + e);
-            }
-            SDLActivity.mSDLThread = null;
-
-            //Log.v(TAG, "Finished waiting for SDL thread");
-        }
-
-        super.onDestroy();
-
-        // Reset everything in case the user re opens the app
-        SDLActivity.initialize();
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-
-        if (SDLActivity.mBrokenLibraries) {
-           return false;
-        }
-
-        int keyCode = event.getKeyCode();
-        // Ignore certain special keys so they're handled by Android
-        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
-            keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
-            keyCode == KeyEvent.KEYCODE_CAMERA ||
-            keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */
-            keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */
-            ) {
-            return false;
-        }
-        return super.dispatchKeyEvent(event);
-    }
-
-    /* Transition to next state */
-    public static void handleNativeState() {
-
-        if (mNextNativeState == mCurrentNativeState) {
-            // Already in same state, discard.
-            return;
-        }
-
-        // Try a transition to init state
-        if (mNextNativeState == NativeState.INIT) {
-
-            mCurrentNativeState = mNextNativeState;
-            return;
-        }
-
-        // Try a transition to paused state
-        if (mNextNativeState == NativeState.PAUSED) {
-            nativePause();
-            mSurface.handlePause();
-            mCurrentNativeState = mNextNativeState;
-            return;
-        }
-
-        // Try a transition to resumed state
-        if (mNextNativeState == NativeState.RESUMED) {
-            if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) {
-                if (mSDLThread == null) {
-                    // This is the entry point to the C app.
-                    // Start up the C app thread and enable sensor input for the first time
-                    // FIXME: Why aren't we enabling sensor input at start?
-
-                    final Thread sdlThread = new Thread(new SDLMain(), "SDLThread");
-                    mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
-                    sdlThread.start();
-
-                    // Set up a listener thread to catch when the native thread ends
-                    mSDLThread = new Thread(new Runnable() {
-                        @Override
-                        public void run() {
-                            try {
-                                sdlThread.join();
-                            } catch (Exception e) {
-                                // Ignore any exception
-                            } finally {
-                                // Native thread has finished
-                                if (!mExitCalledFromJava) {
-                                    handleNativeExit();
-                                }
-                            }
-                        }
-                    }, "SDLThreadListener");
-
-                    mSDLThread.start();
-                }
-
-                nativeResume();
-                mSurface.handleResume();
-                mCurrentNativeState = mNextNativeState;
-            }
-        }
-    }
-
-    /* The native thread has finished */
-    public static void handleNativeExit() {
-        SDLActivity.mSDLThread = null;
-        mSingleton.finish();
-    }
-
-
-    // Messages from the SDLMain thread
-    static final int COMMAND_CHANGE_TITLE = 1;
-    static final int COMMAND_UNUSED = 2;
-    static final int COMMAND_TEXTEDIT_HIDE = 3;
-    static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
-
-    protected static final int COMMAND_USER = 0x8000;
-
-    /**
-     * This method is called by SDL if SDL did not handle a message itself.
-     * This happens if a received message contains an unsupported command.
-     * Method can be overwritten to handle Messages in a different class.
-     * @param command the command of the message.
-     * @param param the parameter of the message. May be null.
-     * @return if the message was handled in overridden method.
-     */
-    protected boolean onUnhandledMessage(int command, Object param) {
-        return false;
-    }
-
-    /**
-     * A Handler class for Messages from native SDL applications.
-     * It uses current Activities as target (e.g. for the title).
-     * static to prevent implicit references to enclosing object.
-     */
-    protected static class SDLCommandHandler extends Handler {
-        @Override
-        public void handleMessage(Message msg) {
-            Context context = SDL.getContext();
-            if (context == null) {
-                Log.e(TAG, "error handling message, getContext() returned null");
-                return;
-            }
-            switch (msg.arg1) {
-            case COMMAND_CHANGE_TITLE:
-                if (context instanceof Activity) {
-                    ((Activity) context).setTitle((String)msg.obj);
-                } else {
-                    Log.e(TAG, "error handling message, getContext() returned no Activity");
-                }
-                break;
-            case COMMAND_TEXTEDIT_HIDE:
-                if (mTextEdit != null) {
-                    // Note: On some devices setting view to GONE creates a flicker in landscape.
-                    // Setting the View's sizes to 0 is similar to GONE but without the flicker.
-                    // The sizes will be set to useful values when the keyboard is shown again.
-                    mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));
-
-                    InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
-                    imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
-                    
-                    mScreenKeyboardShown = false;
-                }
-                break;
-            case COMMAND_SET_KEEP_SCREEN_ON:
-            {
-                if (context instanceof Activity) {
-                    Window window = ((Activity) context).getWindow();
-                    if (window != null) {
-                        if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
-                            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-                        } else {
-                            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-                        }
-                    }
-                }
-                break;
-            }
-            default:
-                if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
-                    Log.e(TAG, "error handling message, command is " + msg.arg1);
-                }
-            }
-        }
-    }
-
-    // Handler for the messages
-    Handler commandHandler = new SDLCommandHandler();
-
-    // Send a message from the SDLMain thread
-    boolean sendCommand(int command, Object data) {
-        Message msg = commandHandler.obtainMessage();
-        msg.arg1 = command;
-        msg.obj = data;
-        return commandHandler.sendMessage(msg);
-    }
-
-    // C functions we call
-    public static native int nativeSetupJNI();
-    public static native int nativeRunMain(String library, String function, Object arguments);
-    public static native void nativeLowMemory();
-    public static native void nativeQuit();
-    public static native void nativePause();
-    public static native void nativeResume();
-    public static native void onNativeDropFile(String filename);
-    public static native void onNativeResize(int x, int y, int format, float rate);
-    public static native void onNativeKeyDown(int keycode);
-    public static native void onNativeKeyUp(int keycode);
-    public static native void onNativeKeyboardFocusLost();
-    public static native void onNativeMouse(int button, int action, float x, float y);
-    public static native void onNativeTouch(int touchDevId, int pointerFingerId,
-                                            int action, float x,
-                                            float y, float p);
-    public static native void onNativeAccel(float x, float y, float z);
-    public static native void onNativeClipboardChanged();
-    public static native void onNativeSurfaceChanged();
-    public static native void onNativeSurfaceDestroyed();
-    public static native String nativeGetHint(String name);
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static boolean setActivityTitle(String title) {
-        // Called from SDLMain() thread and can't directly affect the view
-        return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     * This is a static method for JNI convenience, it calls a non-static method
-     * so that is can be overridden  
-     */
-    public static void setOrientation(int w, int h, boolean resizable, String hint)
-    {
-        if (mSingleton != null) {
-            mSingleton.setOrientationBis(w, h, resizable, hint);
-        }
-    }
-   
-    /**
-     * This can be overridden
-     */
-    public void setOrientationBis(int w, int h, boolean resizable, String hint) 
-    {
-      int orientation = -1;
-
-      if (!Objects.equals(hint, "")) {
-         if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) {
-            orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
-         } else if (hint.contains("LandscapeRight")) {
-            orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
-         } else if (hint.contains("LandscapeLeft")) {
-            orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
-         } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) {
-            orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
-         } else if (hint.contains("Portrait")) {
-            orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-         } else if (hint.contains("PortraitUpsideDown")) {
-            orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
-         }
-      }
-
-      /* no valid hint */
-      if (orientation == -1) {
-         if (resizable) {
-            /* no fixed orientation */
-         } else {
-            if (w > h) {
-               orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
-            } else {
-               orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
-            }
-         }
-      }
-
-      Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint);
-      if (orientation != -1) {
-         mSingleton.setRequestedOrientation(orientation);
-      }
-    }
-
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static boolean isScreenKeyboardShown() 
-    {
-        if (mTextEdit == null) {
-            return false;
-        }
-
-        if (!mScreenKeyboardShown) {
-            return false;
-        }
-
-        InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-        return imm.isAcceptingText();
-
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static boolean sendMessage(int command, int param) {
-        if (mSingleton == null) {
-            return false;
-        }
-        return mSingleton.sendCommand(command, Integer.valueOf(param));
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static Context getContext() {
-        return SDL.getContext();
-    }
-
-    static class ShowTextInputTask implements Runnable {
-        /*
-         * This is used to regulate the pan&scan method to have some offset from
-         * the bottom edge of the input region and the top edge of an input
-         * method (soft keyboard)
-         */
-        static final int HEIGHT_PADDING = 15;
-
-        public int x, y, w, h;
-
-        public ShowTextInputTask(int x, int y, int w, int h) {
-            this.x = x;
-            this.y = y;
-            this.w = w;
-            this.h = h;
-        }
-
-        @Override
-        public void run() {
-            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);
-            params.leftMargin = x;
-            params.topMargin = y;
-
-            if (mTextEdit == null) {
-                mTextEdit = new DummyEdit(SDL.getContext());
-
-                mLayout.addView(mTextEdit, params);
-            } else {
-                mTextEdit.setLayoutParams(params);
-            }
-
-            mTextEdit.setVisibility(View.VISIBLE);
-            mTextEdit.requestFocus();
-
-            InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-            imm.showSoftInput(mTextEdit, 0);
-
-            mScreenKeyboardShown = true;
-        }
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static boolean showTextInput(int x, int y, int w, int h) {
-        // Transfer the task to the main thread as a Runnable
-        return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
-    }
-
-    public static boolean isTextInputEvent(KeyEvent event) {
-      
-        // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT
-        if (android.os.Build.VERSION.SDK_INT >= 11) {
-            if (event.isCtrlPressed()) {
-                return false;
-            }  
-        }
-
-        return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static Surface getNativeSurface() {
-        if (SDLActivity.mSurface == null) {
-            return null;
-        }
-        return SDLActivity.mSurface.getNativeSurface();
-    }
-
-    // Input
-
-    /**
-     * This method is called by SDL using JNI.
-     * @return an array which may be empty but is never null.
-     */
-    public static int[] inputGetInputDeviceIds(int sources) {
-        int[] ids = InputDevice.getDeviceIds();
-        int[] filtered = new int[ids.length];
-        int used = 0;
-        for (int i = 0; i < ids.length; ++i) {
-            InputDevice device = InputDevice.getDevice(ids[i]);
-            if ((device != null) && ((device.getSources() & sources) != 0)) {
-                filtered[used++] = device.getId();
-            }
-        }
-        return Arrays.copyOf(filtered, used);
-    }
-
-    // APK expansion files support
-
-    /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
-    private static Object expansionFile;
-
-    /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
-    private static Method expansionFileMethod;
-
-    /**
-     * This method is called by SDL using JNI.
-     * @return an InputStream on success or null if no expansion file was used.
-     * @throws IOException on errors. Message is set for the SDL error message.
-     */
-    public static InputStream openAPKExpansionInputStream(String fileName) throws IOException {
-        // Get a ZipResourceFile representing a merger of both the main and patch files
-        if (expansionFile == null) {
-            String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
-            if (mainHint == null) {
-                return null; // no expansion use if no main version was set
-            }
-            String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
-            if (patchHint == null) {
-                return null; // no expansion use if no patch version was set
-            }
-
-            Integer mainVersion;
-            Integer patchVersion;
-            try {
-                mainVersion = Integer.valueOf(mainHint);
-                patchVersion = Integer.valueOf(patchHint);
-            } catch (NumberFormatException ex) {
-                ex.printStackTrace();
-                throw new IOException("No valid file versions set for APK expansion files", ex);
-            }
-
-            try {
-                // To avoid direct dependency on Google APK expansion library that is
-                // not a part of Android SDK we access it using reflection
-                expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
-                    .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
-                    .invoke(null, SDL.getContext(), mainVersion, patchVersion);
-
-                expansionFileMethod = expansionFile.getClass()
-                    .getMethod("getInputStream", String.class);
-            } catch (Exception ex) {
-                ex.printStackTrace();
-                expansionFile = null;
-                expansionFileMethod = null;
-                throw new IOException("Could not access APK expansion support library", ex);
-            }
-        }
-
-        // Get an input stream for a known file inside the expansion file ZIPs
-        InputStream fileStream;
-        try {
-            fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
-        } catch (Exception ex) {
-            // calling "getInputStream" failed
-            ex.printStackTrace();
-            throw new IOException("Could not open stream from APK expansion file", ex);
-        }
-
-        if (fileStream == null) {
-            // calling "getInputStream" was successful but null was returned
-            throw new IOException("Could not find path in APK expansion file");
-        }
-
-        return fileStream;
-    }
-
-    // Messagebox
-
-    /** Result of current messagebox. Also used for blocking the calling thread. */
-    protected final int[] messageboxSelection = new int[1];
-
-    /** Id of current dialog. */
-    protected int dialogs = 0;
-
-    /**
-     * This method is called by SDL using JNI.
-     * Shows the messagebox from UI thread and block calling thread.
-     * buttonFlags, buttonIds and buttonTexts must have same length.
-     * @param buttonFlags array containing flags for every button.
-     * @param buttonIds array containing id for every button.
-     * @param buttonTexts array containing text for every button.
-     * @param colors null for default or array of length 5 containing colors.
-     * @return button id or -1.
-     */
-    public int messageboxShowMessageBox(
-            final int flags,
-            final String title,
-            final String message,
-            final int[] buttonFlags,
-            final int[] buttonIds,
-            final String[] buttonTexts,
-            final int[] colors) {
-
-        messageboxSelection[0] = -1;
-
-        // sanity checks
-
-        if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
-            return -1; // implementation broken
-        }
-
-        // collect arguments for Dialog
-
-        final Bundle args = new Bundle();
-        args.putInt("flags", flags);
-        args.putString("title", title);
-        args.putString("message", message);
-        args.putIntArray("buttonFlags", buttonFlags);
-        args.putIntArray("buttonIds", buttonIds);
-        args.putStringArray("buttonTexts", buttonTexts);
-        args.putIntArray("colors", colors);
-
-        // trigger Dialog creation on UI thread
-
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                showDialog(dialogs++, args);
-            }
-        });
-
-        // block the calling thread
-
-        synchronized (messageboxSelection) {
-            try {
-                messageboxSelection.wait();
-            } catch (InterruptedException ex) {
-                ex.printStackTrace();
-                return -1;
-            }
-        }
-
-        // return selected value
-
-        return messageboxSelection[0];
-    }
-
-    @Override
-    protected Dialog onCreateDialog(int ignore, Bundle args) {
-
-        // TODO set values from "flags" to messagebox dialog
-
-        // get colors
-
-        int[] colors = args.getIntArray("colors");
-        int backgroundColor;
-        int textColor;
-        int buttonBorderColor;
-        int buttonBackgroundColor;
-        int buttonSelectedColor;
-        if (colors != null) {
-            int i = -1;
-            backgroundColor = colors[++i];
-            textColor = colors[++i];
-            buttonBorderColor = colors[++i];
-            buttonBackgroundColor = colors[++i];
-            buttonSelectedColor = colors[++i];
-        } else {
-            backgroundColor = Color.TRANSPARENT;
-            textColor = Color.TRANSPARENT;
-            buttonBorderColor = Color.TRANSPARENT;
-            buttonBackgroundColor = Color.TRANSPARENT;
-            buttonSelectedColor = Color.TRANSPARENT;
-        }
-
-        // create dialog with title and a listener to wake up calling thread
-
-        final Dialog dialog = new Dialog(this);
-        dialog.setTitle(args.getString("title"));
-        dialog.setCancelable(false);
-        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
-            @Override
-            public void onDismiss(DialogInterface unused) {
-                synchronized (messageboxSelection) {
-                    messageboxSelection.notify();
-                }
-            }
-        });
-
-        // create text
-
-        TextView message = new TextView(this);
-        message.setGravity(Gravity.CENTER);
-        message.setText(args.getString("message"));
-        if (textColor != Color.TRANSPARENT) {
-            message.setTextColor(textColor);
-        }
-
-        // create buttons
-
-        int[] buttonFlags = args.getIntArray("buttonFlags");
-        int[] buttonIds = args.getIntArray("buttonIds");
-        String[] buttonTexts = args.getStringArray("buttonTexts");
-
-        final SparseArray<Button> mapping = new SparseArray<Button>();
-
-        LinearLayout buttons = new LinearLayout(this);
-        buttons.setOrientation(LinearLayout.HORIZONTAL);
-        buttons.setGravity(Gravity.CENTER);
-        for (int i = 0; i < buttonTexts.length; ++i) {
-            Button button = new Button(this);
-            final int id = buttonIds[i];
-            button.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    messageboxSelection[0] = id;
-                    dialog.dismiss();
-                }
-            });
-            if (buttonFlags[i] != 0) {
-                // see SDL_messagebox.h
-                if ((buttonFlags[i] & 0x00000001) != 0) {
-                    mapping.put(KeyEvent.KEYCODE_ENTER, button);
-                }
-                if ((buttonFlags[i] & 0x00000002) != 0) {
-                    mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */
-                }
-            }
-            button.setText(buttonTexts[i]);
-            if (textColor != Color.TRANSPARENT) {
-                button.setTextColor(textColor);
-            }
-            if (buttonBorderColor != Color.TRANSPARENT) {
-                // TODO set color for border of messagebox button
-            }
-            if (buttonBackgroundColor != Color.TRANSPARENT) {
-                Drawable drawable = button.getBackground();
-                if (drawable == null) {
-                    // setting the color this way removes the style
-                    button.setBackgroundColor(buttonBackgroundColor);
-                } else {
-                    // setting the color this way keeps the style (gradient, padding, etc.)
-                    drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);
-                }
-            }
-            if (buttonSelectedColor != Color.TRANSPARENT) {
-                // TODO set color for selected messagebox button
-            }
-            buttons.addView(button);
-        }
-
-        // create content
-
-        LinearLayout content = new LinearLayout(this);
-        content.setOrientation(LinearLayout.VERTICAL);
-        content.addView(message);
-        content.addView(buttons);
-        if (backgroundColor != Color.TRANSPARENT) {
-            content.setBackgroundColor(backgroundColor);
-        }
-
-        // add content to dialog and return
-
-        dialog.setContentView(content);
-        dialog.setOnKeyListener(new Dialog.OnKeyListener() {
-            @Override
-            public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {
-                Button button = mapping.get(keyCode);
-                if (button != null) {
-                    if (event.getAction() == KeyEvent.ACTION_UP) {
-                        button.performClick();
-                    }
-                    return true; // also for ignored actions
-                }
-                return false;
-            }
-        });
-
-        return dialog;
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static boolean clipboardHasText() {
-        return mClipboardHandler.clipboardHasText();
-    }
-    
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static String clipboardGetText() {
-        return mClipboardHandler.clipboardGetText();
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static void clipboardSetText(String string) {
-        mClipboardHandler.clipboardSetText(string);
-    }
-
-}
-
-/**
-    Simple runnable to start the SDL application
-*/
-class SDLMain implements Runnable {
-    @Override
-    public void run() {
-        // Runs SDL_main()
-        String library = SDLActivity.mSingleton.getMainSharedObject();
-        String function = SDLActivity.mSingleton.getMainFunction();
-        String[] arguments = SDLActivity.mSingleton.getArguments();
-
-        Log.v("SDL", "Running main function " + function + " from library " + library);
-        SDLActivity.nativeRunMain(library, function, arguments);
-
-        Log.v("SDL", "Finished main function");
-    }
-}
-
-
-/**
-    SDLSurface. This is what we draw on, so we need to know when it's created
-    in order to do anything useful.
-
-    Because of this, that's where we set up the SDL thread
-*/
-class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
-    View.OnKeyListener, View.OnTouchListener, SensorEventListener  {
-
-    // Sensors
-    protected static SensorManager mSensorManager;
-    protected static Display mDisplay;
-
-    // Keep track of the surface size to normalize touch events
-    protected static float mWidth, mHeight;
-
-    // Startup
-    public SDLSurface(Context context) {
-        super(context);
-        getHolder().addCallback(this);
-
-        setFocusable(true);
-        setFocusableInTouchMode(true);
-        requestFocus();
-        setOnKeyListener(this);
-        setOnTouchListener(this);
-
-        mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
-        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
-
-        if (Build.VERSION.SDK_INT >= 12) {
-            setOnGenericMotionListener(new SDLGenericMotionListener_API12());
-        }
-
-        // Some arbitrary defaults to avoid a potential division by zero
-        mWidth = 1.0f;
-        mHeight = 1.0f;
-    }
-
-    public void handlePause() {
-        enableSensor(Sensor.TYPE_ACCELEROMETER, false);
-    }
-
-    public void handleResume() {
-        setFocusable(true);
-        setFocusableInTouchMode(true);
-        requestFocus();
-        setOnKeyListener(this);
-        setOnTouchListener(this);
-        enableSensor(Sensor.TYPE_ACCELEROMETER, true);
-    }
-
-    public Surface getNativeSurface() {
-        return getHolder().getSurface();
-    }
-
-    // Called when we have a valid drawing surface
-    @Override
-    public void surfaceCreated(SurfaceHolder holder) {
-        Log.v("SDL", "surfaceCreated()");
-        holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
-    }
-
-    // Called when we lose the surface
-    @Override
-    public void surfaceDestroyed(SurfaceHolder holder) {
-        Log.v("SDL", "surfaceDestroyed()");
-
-        // Transition to pause, if needed
-        SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
-        SDLActivity.handleNativeState();
-
-        SDLActivity.mIsSurfaceReady = false;
-        SDLActivity.onNativeSurfaceDestroyed();
-    }
-
-    // Called when the surface is resized
-    @Override
-    public void surfaceChanged(SurfaceHolder holder,
-                               int format, int width, int height) {
-        Log.v("SDL", "surfaceChanged()");
-
-        int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
-        switch (format) {
-        case PixelFormat.A_8:
-            Log.v("SDL", "pixel format A_8");
-            break;
-        case PixelFormat.LA_88:
-            Log.v("SDL", "pixel format LA_88");
-            break;
-        case PixelFormat.L_8:
-            Log.v("SDL", "pixel format L_8");
-            break;
-        case PixelFormat.RGBA_4444:
-            Log.v("SDL", "pixel format RGBA_4444");
-            sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
-            break;
-        case PixelFormat.RGBA_5551:
-            Log.v("SDL", "pixel format RGBA_5551");
-            sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
-            break;
-        case PixelFormat.RGBA_8888:
-            Log.v("SDL", "pixel format RGBA_8888");
-            sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
-            break;
-        case PixelFormat.RGBX_8888:
-            Log.v("SDL", "pixel format RGBX_8888");
-            sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
-            break;
-        case PixelFormat.RGB_332:
-            Log.v("SDL", "pixel format RGB_332");
-            sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
-            break;
-        case PixelFormat.RGB_565:
-            Log.v("SDL", "pixel format RGB_565");
-            sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
-            break;
-        case PixelFormat.RGB_888:
-            Log.v("SDL", "pixel format RGB_888");
-            // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
-            sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
-            break;
-        default:
-            Log.v("SDL", "pixel format unknown " + format);
-            break;
-        }
-
-        mWidth = width;
-        mHeight = height;
-        SDLActivity.onNativeResize(width, height, sdlFormat, mDisplay.getRefreshRate());
-        Log.v("SDL", "Window size: " + width + "x" + height);
-
- 
-        boolean skip = false;
-        int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
-
-        if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
-        {
-            // Accept any
-        }
-        else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
-        {
-            if (mWidth > mHeight) {
-               skip = true;
-            }
-        } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
-            if (mWidth < mHeight) {
-               skip = true;
-            }
-        }
-
-        // Special Patch for Square Resolution: Black Berry Passport
-        if (skip) {
-           double min = Math.min(mWidth, mHeight);
-           double max = Math.max(mWidth, mHeight);
-           
-           if (max / min < 1.20) {
-              Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
-              skip = false;
-           }
-        }
-
-        if (skip) {
-           Log.v("SDL", "Skip .. Surface is not ready.");
-           SDLActivity.mIsSurfaceReady = false;
-           return;
-        }
-        
-        /* Surface is ready */
-        SDLActivity.mIsSurfaceReady = true;
-
-        /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
-        SDLActivity.onNativeSurfaceChanged();
-
-        SDLActivity.handleNativeState();
-    }
-
-    // Key events
-    @Override
-    public boolean onKey(View  v, int keyCode, KeyEvent event) {
-        // Dispatch the different events depending on where they come from
-        // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
-        // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
-        //
-        // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and
-        // SOURCE_JOYSTICK, while its key events arrive from the keyboard source
-        // So, retrieve the device itself and check all of its sources
-        if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) {
-            // Note that we process events with specific key codes here
-            if (event.getAction() == KeyEvent.ACTION_DOWN) {
-                if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
-                    return true;
-                }
-            } else if (event.getAction() == KeyEvent.ACTION_UP) {
-                if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
-                    return true;
-                }
-            }
-        }
-
-        if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
-            if (event.getAction() == KeyEvent.ACTION_DOWN) {
-                //Log.v("SDL", "key down: " + keyCode);
-                SDLActivity.onNativeKeyDown(keyCode);
-                return true;
-            }
-            else if (event.getAction() == KeyEvent.ACTION_UP) {
-                //Log.v("SDL", "key up: " + keyCode);
-                SDLActivity.onNativeKeyUp(keyCode);
-                return true;
-            }
-        }
-
-        if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) {
-            // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
-            // they are ignored here because sending them as mouse input to SDL is messy
-            if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
-                switch (event.getAction()) {
-                case KeyEvent.ACTION_DOWN:
-                case KeyEvent.ACTION_UP:
-                    // mark the event as handled or it will be handled by system
-                    // handling KEYCODE_BACK by system will call onBackPressed()
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    // Touch events
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        /* Ref: http://developer.android.com/training/gestures/multi.html */
-        final int touchDevId = event.getDeviceId();
-        final int pointerCount = event.getPointerCount();
-        int action = event.getActionMasked();
-        int pointerFingerId;
-        int mouseButton;
-        int i = -1;
-        float x,y,p;
-
-        // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14.
-        if (event.getSource() == InputDevice.SOURCE_MOUSE && SDLActivity.mSeparateMouseAndTouch) {
-            if (Build.VERSION.SDK_INT < 14) {
-                mouseButton = 1; // all mouse buttons are the left button
-            } else {
-                try {
-                    mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event);
-                } catch(Exception e) {
-                    mouseButton = 1;    // oh well.
-                }
-            }
-            SDLActivity.onNativeMouse(mouseButton, action, event.getX(0), event.getY(0));
-        } else {
-            switch(action) {
-                case MotionEvent.ACTION_MOVE:
-                    for (i = 0; i < pointerCount; i++) {
-                        pointerFingerId = event.getPointerId(i);
-                        x = event.getX(i) / mWidth;
-                        y = event.getY(i) / mHeight;
-                        p = event.getPressure(i);
-                        if (p > 1.0f) {
-                            // may be larger than 1.0f on some devices
-                            // see the documentation of getPressure(i)
-                            p = 1.0f;
-                        }
-                        SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
-                    }
-                    break;
-
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_DOWN:
-                    // Primary pointer up/down, the index is always zero
-                    i = 0;
-                case MotionEvent.ACTION_POINTER_UP:
-                case MotionEvent.ACTION_POINTER_DOWN:
-                    // Non primary pointer up/down
-                    if (i == -1) {
-                        i = event.getActionIndex();
-                    }
-
-                    pointerFingerId = event.getPointerId(i);
-                    x = event.getX(i) / mWidth;
-                    y = event.getY(i) / mHeight;
-                    p = event.getPressure(i);
-                    if (p > 1.0f) {
-                        // may be larger than 1.0f on some devices
-                        // see the documentation of getPressure(i)
-                        p = 1.0f;
-                    }
-                    SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
-                    break;
-
-                case MotionEvent.ACTION_CANCEL:
-                    for (i = 0; i < pointerCount; i++) {
-                        pointerFingerId = event.getPointerId(i);
-                        x = event.getX(i) / mWidth;
-                        y = event.getY(i) / mHeight;
-                        p = event.getPressure(i);
-                        if (p > 1.0f) {
-                            // may be larger than 1.0f on some devices
-                            // see the documentation of getPressure(i)
-                            p = 1.0f;
-                        }
-                        SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
-                    }
-                    break;
-
-                default:
-                    break;
-            }
-        }
-
-        return true;
-   }
-
-    // Sensor events
-    public void enableSensor(int sensortype, boolean enabled) {
-        // TODO: This uses getDefaultSensor - what if we have >1 accels?
-        if (enabled) {
-            mSensorManager.registerListener(this,
-                            mSensorManager.getDefaultSensor(sensortype),
-                            SensorManager.SENSOR_DELAY_GAME, null);
-        } else {
-            mSensorManager.unregisterListener(this,
-                            mSensorManager.getDefaultSensor(sensortype));
-        }
-    }
-
-    @Override
-    public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        // TODO
-    }
-
-    @Override
-    public void onSensorChanged(SensorEvent event) {
-        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
-            float x, y;
-            switch (mDisplay.getRotation()) {
-                case Surface.ROTATION_90:
-                    x = -event.values[1];
-                    y = event.values[0];
-                    break;
-                case Surface.ROTATION_270:
-                    x = event.values[1];
-                    y = -event.values[0];
-                    break;
-                case Surface.ROTATION_180:
-                    x = -event.values[1];
-                    y = -event.values[0];
-                    break;
-                default:
-                    x = event.values[0];
-                    y = event.values[1];
-                    break;
-            }
-            SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
-                                      y / SensorManager.GRAVITY_EARTH,
-                                      event.values[2] / SensorManager.GRAVITY_EARTH);
-        }
-    }
-}
-
-/* This is a fake invisible editor view that receives the input and defines the
- * pan&scan region
- */
-class DummyEdit extends View implements View.OnKeyListener {
-    InputConnection ic;
-
-    public DummyEdit(Context context) {
-        super(context);
-        setFocusableInTouchMode(true);
-        setFocusable(true);
-        setOnKeyListener(this);
-    }
-
-    @Override
-    public boolean onCheckIsTextEditor() {
-        return true;
-    }
-
-    @Override
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        /* 
-         * This handles the hardware keyboard input
-         */
-        if (event.getAction() == KeyEvent.ACTION_DOWN) {
-            if (SDLActivity.isTextInputEvent(event)) {
-                ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
-            }
-            SDLActivity.onNativeKeyDown(keyCode);
-            return true;
-        } else if (event.getAction() == KeyEvent.ACTION_UP) {
-            SDLActivity.onNativeKeyUp(keyCode);
-            return true;
-        }
-        return false;
-    }
-
-    //
-    @Override
-    public boolean onKeyPreIme (int keyCode, KeyEvent event) {
-        // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
-        // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
-        // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
-        // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
-        // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
-        // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
-        if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
-            if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
-                SDLActivity.onNativeKeyboardFocusLost();
-            }
-        }
-        return super.onKeyPreIme(keyCode, event);
-    }
-
-    @Override
-    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-        ic = new SDLInputConnection(this, true);
-
-        outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
-        outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
-                | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
-
-        return ic;
-    }
-}
-
-class SDLInputConnection extends BaseInputConnection {
-
-    public SDLInputConnection(View targetView, boolean fullEditor) {
-        super(targetView, fullEditor);
-
-    }
-
-    @Override
-    public boolean sendKeyEvent(KeyEvent event) {
-        /*
-         * This handles the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
-         */
-        int keyCode = event.getKeyCode();
-        if (event.getAction() == KeyEvent.ACTION_DOWN) {
-            if (SDLActivity.isTextInputEvent(event)) {
-                commitText(String.valueOf((char) event.getUnicodeChar()), 1);
-            }
-            SDLActivity.onNativeKeyDown(keyCode);
-            return true;
-        } else if (event.getAction() == KeyEvent.ACTION_UP) {
-            SDLActivity.onNativeKeyUp(keyCode);
-            return true;
-        }
-        return super.sendKeyEvent(event);
-    }
-
-    @Override
-    public boolean commitText(CharSequence text, int newCursorPosition) {
-
-        nativeCommitText(text.toString(), newCursorPosition);
-
-        return super.commitText(text, newCursorPosition);
-    }
-
-    @Override
-    public boolean setComposingText(CharSequence text, int newCursorPosition) {
-
-        nativeSetComposingText(text.toString(), newCursorPosition);
-
-        return super.setComposingText(text, newCursorPosition);
-    }
-
-    public native void nativeCommitText(String text, int newCursorPosition);
-
-    public native void nativeSetComposingText(String text, int newCursorPosition);
-
-    @Override
-    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
-        // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
-        // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
-        if (beforeLength > 0 && afterLength == 0) {
-            boolean ret = true;
-            // backspace(s)
-            while (beforeLength-- > 0) {
-               boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
-                              && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
-               ret = ret && ret_key; 
-            }
-            return ret;
-        }
-
-        return super.deleteSurroundingText(beforeLength, afterLength);
-    }
-}
-
-interface SDLClipboardHandler {
-
-    public boolean clipboardHasText();
-    public String clipboardGetText();
-    public void clipboardSetText(String string);
-
-}
-
-
-class SDLClipboardHandler_API11 implements
-    SDLClipboardHandler, 
-    android.content.ClipboardManager.OnPrimaryClipChangedListener {
-
-    protected android.content.ClipboardManager mClipMgr;
-
-    SDLClipboardHandler_API11() {
-       mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
-       mClipMgr.addPrimaryClipChangedListener(this);
-    }
-
-    @Override
-    public boolean clipboardHasText() {
-       return mClipMgr.hasText();
-    }
-
-    @Override
-    public String clipboardGetText() {
-        CharSequence text;
-        text = mClipMgr.getText();
-        if (text != null) {
-           return text.toString();
-        }
-        return null;
-    }
-
-    @Override
-    public void clipboardSetText(String string) {
-       mClipMgr.removePrimaryClipChangedListener(this);
-       mClipMgr.setText(string);
-       mClipMgr.addPrimaryClipChangedListener(this);
-    }
-    
-    @Override
-    public void onPrimaryClipChanged() {
-        SDLActivity.onNativeClipboardChanged();
-    }
-
-}
-
-class SDLClipboardHandler_Old implements
-    SDLClipboardHandler {
-   
-    protected android.text.ClipboardManager mClipMgrOld;
-  
-    SDLClipboardHandler_Old() {
-       mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
-    }
-
-    @Override
-    public boolean clipboardHasText() {
-       return mClipMgrOld.hasText();
-    }
-
-    @Override
-    public String clipboardGetText() {
-       CharSequence text;
-       text = mClipMgrOld.getText();
-       if (text != null) {
-          return text.toString();
-       }
-       return null;
-    }
-
-    @Override
-    public void clipboardSetText(String string) {
-       mClipMgrOld.setText(string);
-    }
-}
-
--- a/android/src/org/libsdl/app/SDLAudioManager.java	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-package org.libsdl.app;
-
-import android.media.*;
-import android.util.Log;
-
-public class SDLAudioManager
-{
-    protected static final String TAG = "SDLAudio";
-
-    protected static AudioTrack mAudioTrack;
-    protected static AudioRecord mAudioRecord;
-
-    public static void initialize() {
-        mAudioTrack = null;
-        mAudioRecord = null;
-    }
-
-    // Audio
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static int audioOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
-        int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
-        int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
-
-        Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-
-        // Let the user pick a larger buffer if they really want -- but ye
-        // gods they probably shouldn't, the minimums are horrifyingly high
-        // latency already
-        desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
-
-        if (mAudioTrack == null) {
-            mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
-                    channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
-
-            // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
-            // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
-            // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
-
-            if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
-                Log.e(TAG, "Failed during initialization of Audio Track");
-                mAudioTrack = null;
-                return -1;
-            }
-
-            mAudioTrack.play();
-        }
-
-        Log.v(TAG, "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-
-        return 0;
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static void audioWriteShortBuffer(short[] buffer) {
-        if (mAudioTrack == null) {
-            Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
-            return;
-        }
-
-        for (int i = 0; i < buffer.length; ) {
-            int result = mAudioTrack.write(buffer, i, buffer.length - i);
-            if (result > 0) {
-                i += result;
-            } else if (result == 0) {
-                try {
-                    Thread.sleep(1);
-                } catch(InterruptedException e) {
-                    // Nom nom
-                }
-            } else {
-                Log.w(TAG, "SDL audio: error return from write(short)");
-                return;
-            }
-        }
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static void audioWriteByteBuffer(byte[] buffer) {
-        if (mAudioTrack == null) {
-            Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
-            return;
-        }
-        
-        for (int i = 0; i < buffer.length; ) {
-            int result = mAudioTrack.write(buffer, i, buffer.length - i);
-            if (result > 0) {
-                i += result;
-            } else if (result == 0) {
-                try {
-                    Thread.sleep(1);
-                } catch(InterruptedException e) {
-                    // Nom nom
-                }
-            } else {
-                Log.w(TAG, "SDL audio: error return from write(byte)");
-                return;
-            }
-        }
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static int captureOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
-        int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
-        int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
-
-        Log.v(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-
-        // Let the user pick a larger buffer if they really want -- but ye
-        // gods they probably shouldn't, the minimums are horrifyingly high
-        // latency already
-        desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
-
-        if (mAudioRecord == null) {
-            mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
-                    channelConfig, audioFormat, desiredFrames * frameSize);
-
-            // see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
-            if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
-                Log.e(TAG, "Failed during initialization of AudioRecord");
-                mAudioRecord.release();
-                mAudioRecord = null;
-                return -1;
-            }
-
-            mAudioRecord.startRecording();
-        }
-
-        Log.v(TAG, "SDL capture: got " + ((mAudioRecord.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioRecord.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-
-        return 0;
-    }
-
-    /** This method is called by SDL using JNI. */
-    public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
-        // !!! FIXME: this is available in API Level 23. Until then, we always block.  :(
-        //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
-        return mAudioRecord.read(buffer, 0, buffer.length);
-    }
-
-    /** This method is called by SDL using JNI. */
-    public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
-        // !!! FIXME: this is available in API Level 23. Until then, we always block.  :(
-        //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
-        return mAudioRecord.read(buffer, 0, buffer.length);
-    }
-
-
-    /** This method is called by SDL using JNI. */
-    public static void audioClose() {
-        if (mAudioTrack != null) {
-            mAudioTrack.stop();
-            mAudioTrack.release();
-            mAudioTrack = null;
-        }
-    }
-
-    /** This method is called by SDL using JNI. */
-    public static void captureClose() {
-        if (mAudioRecord != null) {
-            mAudioRecord.stop();
-            mAudioRecord.release();
-            mAudioRecord = null;
-        }
-    }
-
-    public static native int nativeSetupJNI();
-}
--- a/android/src/org/libsdl/app/SDLControllerManager.java	Wed Mar 26 01:20:09 2025 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,433 +0,0 @@
-package org.libsdl.app;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Objects;
-
-import android.content.Context;
-import android.os.*;
-import android.view.*;
-import android.util.Log;
-
-
-public class SDLControllerManager 
-{
-
-    public static native int nativeSetupJNI();
-
-    public static native int nativeAddJoystick(int device_id, String name, String desc,
-                                               int is_accelerometer, int nbuttons,
-                                               int naxes, int nhats, int nballs);
-    public static native int nativeRemoveJoystick(int device_id);
-    public static native int nativeAddHaptic(int device_id, String name);
-    public static native int nativeRemoveHaptic(int device_id);
-    public static native int onNativePadDown(int device_id, int keycode);
-    public static native int onNativePadUp(int device_id, int keycode);
-    public static native void onNativeJoy(int device_id, int axis,
-                                          float value);
-    public static native void onNativeHat(int device_id, int hat_id,
-                                          int x, int y);
-
-    protected static SDLJoystickHandler mJoystickHandler;
-    protected static SDLHapticHandler mHapticHandler;
-
-    private static final String TAG = "SDLControllerManager";
-
-    public static void initialize() {
-        mJoystickHandler = null;
-        mHapticHandler = null;
-
-        SDLControllerManager.setup();
-    }
-
-    public static void setup() {
-        if (Build.VERSION.SDK_INT >= 16) {
-            mJoystickHandler = new SDLJoystickHandler_API16();
-        } else if (Build.VERSION.SDK_INT >= 12) {
-            mJoystickHandler = new SDLJoystickHandler_API12();
-        } else {
-            mJoystickHandler = new SDLJoystickHandler();
-        }
-        mHapticHandler = new SDLHapticHandler();
-    }
-
-    // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
-    public static boolean handleJoystickMotionEvent(MotionEvent event) {
-        return mJoystickHandler.handleMotionEvent(event);
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static void pollInputDevices() {
-        mJoystickHandler.pollInputDevices();
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static void pollHapticDevices() {
-        mHapticHandler.pollHapticDevices();
-    }
-
-    /**
-     * This method is called by SDL using JNI.
-     */
-    public static void hapticRun(int device_id, int length) {
-        mHapticHandler.run(device_id, length);
-    }
-
-    // Check if a given device is considered a possible SDL joystick
-    public static boolean isDeviceSDLJoystick(int deviceId) {
-        InputDevice device = InputDevice.getDevice(deviceId);
-        // We cannot use InputDevice.isVirtual before API 16, so let's accept
-        // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
-        if ((device == null) || (deviceId < 0)) {
-            return false;
-        }
-        int sources = device.getSources();
-
-        if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) {
-            Log.v(TAG, "Input device " + device.getName() + " is a joystick.");
-        }
-        if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
-            Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
-        }
-        if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
-            Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
-        }
-
-        return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) ||
-                ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
-                ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
-        );
-    }
-
-}
-
-/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
-class SDLJoystickHandler {
-
-    /**
-     * Handles given MotionEvent.
-     * @param event the event to be handled.
-     * @return if given event was processed.
-     */
-    public boolean handleMotionEvent(MotionEvent event) {
-        return false;
-    }
-
-    /**
-     * Handles adding and removing of input devices.
-     */
-    public void pollInputDevices() {
-    }
-}
-
-/* Actual joystick functionality available for API >= 12 devices */
-class SDLJoystickHandler_API12 extends SDLJoystickHandler {
-
-    static class SDLJoystick {
-        public int device_id;
-        public String name;
-        public String desc;
-        public ArrayList<InputDevice.MotionRange> axes;
-        public ArrayList<InputDevice.MotionRange> hats;
-    }
-    static class RangeComparator implements Comparator<InputDevice.MotionRange> {
-        @Override
-        public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
-            return arg0.getAxis() - arg1.getAxis();
-        }
-    }
-
-    private ArrayList<SDLJoystick> mJoysticks;
-
-    public SDLJoystickHandler_API12() {
-
-        mJoysticks = new ArrayList<SDLJoystick>();
-    }
-
-    @Override
-    public void pollInputDevices() {
-        int[] deviceIds = InputDevice.getDeviceIds();
-        // It helps processing the device ids in reverse order
-        // For example, in the case of the XBox 360 wireless dongle,
-        // so the first controller seen by SDL matches what the receiver
-        // considers to be the first controller
-
-        for(int i=deviceIds.length-1; i>-1; i--) {
-            SDLJoystick joystick = getJoystick(deviceIds[i]);
-            if (joystick == null) {
-                joystick = new SDLJoystick();
-                InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
-                if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) {
-                    joystick.device_id = deviceIds[i];
-                    joystick.name = joystickDevice.getName();
-                    joystick.desc = getJoystickDescriptor(joystickDevice);
-                    joystick.axes = new ArrayList<InputDevice.MotionRange>();
-                    joystick.hats = new ArrayList<InputDevice.MotionRange>();
-
-                    List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
-                    Collections.sort(ranges, new RangeComparator());
-                    for (InputDevice.MotionRange range : ranges ) {
-                        if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
-                            if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
-                                range.getAxis() == MotionEvent.AXIS_HAT_Y) {
-                                joystick.hats.add(range);
-                            }
-                            else {
-                                joystick.axes.add(range);
-                            }
-                        }
-                    }
-
-                    mJoysticks.add(joystick);
-                    SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, 0, -1,
-                                                           joystick.axes.size(), joystick.hats.size()/2, 0);
-                }
-            }
-        }
-
-        /* Check removed devices */
-        ArrayList<Integer> removedDevices = new ArrayList<Integer>();
-        for(int i=0; i < mJoysticks.size(); i++) {
-            int device_id = mJoysticks.get(i).device_id;
-            int j;
-            for (j=0; j < deviceIds.length; j++) {
-                if (device_id == deviceIds[j]) break;
-            }
-            if (j == deviceIds.length) {
-                removedDevices.add(Integer.valueOf(device_id));
-            }
-        }
-
-        for(int i=0; i < removedDevices.size(); i++) {
-            int device_id = removedDevices.get(i).intValue();
-            SDLControllerManager.nativeRemoveJoystick(device_id);
-            for (int j=0; j < mJoysticks.size(); j++) {
-                if (mJoysticks.get(j).device_id == device_id) {
-                    mJoysticks.remove(j);
-                    break;
-                }
-            }
-        }
-    }
-
-    protected SDLJoystick getJoystick(int device_id) {
-        for(int i=0; i < mJoysticks.size(); i++) {
-            if (mJoysticks.get(i).device_id == device_id) {
-                return mJoysticks.get(i);
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public boolean handleMotionEvent(MotionEvent event) {
-        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
-            int actionPointerIndex = event.getActionIndex();
-            int action = event.getActionMasked();
-            switch(action) {
-                case MotionEvent.ACTION_MOVE:
-                    SDLJoystick joystick = getJoystick(event.getDeviceId());
-                    if ( joystick != null ) {
-                        for (int i = 0; i < joystick.axes.size(); i++) {
-                            InputDevice.MotionRange range = joystick.axes.get(i);
-                            /* Normalize the value to -1...1 */
-                            float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
-                            SDLControllerManager.onNativeJoy(joystick.device_id, i, value );
-                        }
-                        for (int i = 0; i < joystick.hats.size(); i+=2) {
-                            int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
-                            int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
-                            SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY );
-                        }
-                    }
-                    break;
-                default:
-                    break;
-            }
-        }
-        return true;
-    }
-
-    public String getJoystickDescriptor(InputDevice joystickDevice) {
-        return joystickDevice.getName();
-    }
-}
-
-
-class SDLJoystickHandler_API16 extends SDLJoystickHandler_API12 {
-
-    @Override
-    public String getJoystickDescriptor(InputDevice joystickDevice) {
-        String desc = joystickDevice.getDescriptor();
-
-        if (desc != null && !Objects.equals(desc, "")) {
-            return desc;
-        }
-
-        return super.getJoystickDescriptor(joystickDevice);
-    }
-}
-
-class SDLHapticHandler {
-
-    class SDLHaptic {
-        public int device_id;
-        public String name;
-        public Vibrator vib;
-    }
-
-    private ArrayList<SDLHaptic> mHaptics;
-    
-    public SDLHapticHandler() {
-        mHaptics = new ArrayList<SDLHaptic>();
-    }
-
-    public void run(int device_id, int length) {
-        SDLHaptic haptic = getHaptic(device_id);
-        if (haptic != null) {
-            haptic.vib.vibrate (length);
-        }
-    }
-
-    public void pollHapticDevices() {
-        
-        final int deviceId_VIBRATOR_SERVICE = 999999;
-        boolean hasVibratorService = false;
-
-        int[] deviceIds = InputDevice.getDeviceIds();
-        // It helps processing the device ids in reverse order
-        // For example, in the case of the XBox 360 wireless dongle,
-        // so the first controller seen by SDL matches what the receiver
-        // considers to be the first controller
-
-        if (Build.VERSION.SDK_INT >= 16)
-        {
-            for (int i = deviceIds.length - 1; i > -1; i--) {
-                SDLHaptic haptic = getHaptic(deviceIds[i]);
-                if (haptic == null) {
-                    InputDevice device = InputDevice.getDevice(deviceIds[i]);
-                    Vibrator vib = device.getVibrator();
-                    if (vib.hasVibrator()) {
-                        haptic = new SDLHaptic();
-                        haptic.device_id = deviceIds[i];
-                        haptic.name = device.getName();
-                        haptic.vib = vib;
-                        mHaptics.add(haptic);
-                        SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
-                    }
-                }
-            }
-        }
-
-        /* Check VIBRATOR_SERVICE */
-        Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
-        if (vib != null) {
-            if (Build.VERSION.SDK_INT >= 11) {
-                hasVibratorService = vib.hasVibrator();
-            } else {
-                hasVibratorService = true;
-            }
-
-            if (hasVibratorService) {
-                SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
-                if (haptic == null) {
-                    haptic = new SDLHaptic();
-                    haptic.device_id = deviceId_VIBRATOR_SERVICE;
-                    haptic.name = "VIBRATOR_SERVICE";
-                    haptic.vib = vib; 
-                    mHaptics.add(haptic);
-                    SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
-                }
-            }
-        }
-
-        /* Check removed devices */
-        ArrayList<Integer> removedDevices = new ArrayList<Integer>();
-        for(int i=0; i < mHaptics.size(); i++) {
-            int device_id = mHaptics.get(i).device_id;
-            int j;
-            for (j=0; j < deviceIds.length; j++) {
-                if (device_id == deviceIds[j]) break;
-            }
-
-            if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) {
-                // don't remove the vibrator if it is still present
-            } else if (j == deviceIds.length) {
-                removedDevices.add(device_id);
-            }
-        }
-
-        for(int i=0; i < removedDevices.size(); i++) {
-            int device_id = removedDevices.get(i);
-            SDLControllerManager.nativeRemoveHaptic(device_id);
-            for (int j=0; j < mHaptics.size(); j++) {
-                if (mHaptics.get(j).device_id == device_id) {
-                    mHaptics.remove(j);
-                    break;
-                }
-            }
-        }
-    }
-
-    protected SDLHaptic getHaptic(int device_id) {
-        for(int i=0; i < mHaptics.size(); i++) {
-            if (mHaptics.get(i).device_id == device_id) {
-                return mHaptics.get(i);
-            }
-        }
-        return null;
-    }   
-}
-
-class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
-    // Generic Motion (mouse hover, joystick...) events go here
-    @Override
-    public boolean onGenericMotion(View v, MotionEvent event) {
-        float x, y;
-        int action;
-
-        switch ( event.getSource() ) {
-            case InputDevice.SOURCE_JOYSTICK:
-            case InputDevice.SOURCE_GAMEPAD:
-            case InputDevice.SOURCE_DPAD:
-                return SDLControllerManager.handleJoystickMotionEvent(event);
-
-            case InputDevice.SOURCE_MOUSE:
-                if (!SDLActivity.mSeparateMouseAndTouch) {
-                    break;
-                }
-                action = event.getActionMasked();
-                switch (action) {
-                    case MotionEvent.ACTION_SCROLL:
-                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
-                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
-                        SDLActivity.onNativeMouse(0, action, x, y);
-                        return true;
-
-                    case MotionEvent.ACTION_HOVER_MOVE:
-                        x = event.getX(0);
-                        y = event.getY(0);
-
-                        SDLActivity.onNativeMouse(0, action, x, y);
-                        return true;
-
-                    default:
-                        break;
-                }
-                break;
-
-            default:
-                break;
-        }
-
-        // Event was not managed
-        return false;
-    }
-}
-
--- a/debug.c	Wed Mar 26 01:20:09 2025 -0700
+++ b/debug.c	Wed Mar 26 01:20:55 2025 -0700
@@ -29,7 +29,7 @@
 static debug_func *funcs;
 static uint32_t num_funcs, func_storage;
 
-static debug_func* alloc_func(void)
+static debug_func* alloc_dfunc(void)
 {
 	if (num_funcs == func_storage) {
 		func_storage = func_storage ? func_storage * 2 : 4;
@@ -40,7 +40,7 @@
 
 static debug_val new_native_func(debug_native_func impl, int max_args, int min_args)
 {
-	debug_func *f = alloc_func();
+	debug_func *f = alloc_dfunc();
 	f->impl.native = impl;
 	f->max_args = max_args;
 	f->min_args = min_args;
@@ -55,7 +55,7 @@
 
 static debug_val new_user_func(command_block *block, char **args, int num_args)
 {
-	debug_func *f = alloc_func();
+	debug_func *f = alloc_dfunc();
 	f->impl.block = *block;
 	f->arg_names = args;
 	f->max_args = f->min_args = num_args;
--- a/event_log.c	Wed Mar 26 01:20:09 2025 -0700
+++ b/event_log.c	Wed Mar 26 01:20:55 2025 -0700
@@ -7,6 +7,7 @@
 #include <sys/socket.h>
 #include <unistd.h>
 #include <netdb.h>
+#include <netinet/in.h>
 #include <netinet/tcp.h>
 #endif
 
--- a/nuklear_ui/blastem_nuklear.c	Wed Mar 26 01:20:09 2025 -0700
+++ b/nuklear_ui/blastem_nuklear.c	Wed Mar 26 01:20:55 2025 -0700
@@ -257,7 +257,9 @@
 	static size_t num_entries;
 	static int32_t selected_entry = -1;
 	if (!browser_cur_path) {
-		get_initial_browse_path(&browser_cur_path);
+		if (!get_initial_browse_path(&browser_cur_path)) {
+			return;
+		}
 	}
 	if (use_native_filechooser && native_filechooser_available()) {
 		char *path = native_filechooser_pick(browser_label, browser_cur_path);
--- a/paths.c	Wed Mar 26 01:20:09 2025 -0700
+++ b/paths.c	Wed Mar 26 01:20:55 2025 -0700
@@ -10,6 +10,10 @@
 #include <unistd.h>
 #include <errno.h>
 #endif
+#ifdef __ANDROID__
+#include <SDL_system.h>
+#include <jni.h>
+#endif
 
 static char **current_path;
 
@@ -39,8 +43,6 @@
 }
 
 #ifdef __ANDROID__
-#include <SDL.h>
-#include <jni.h>
 static char *get_external_storage_path()
 {
 	static char *ret;
@@ -74,9 +76,41 @@
 }
 #endif
 
-void get_initial_browse_path(char **dst)
+uint8_t get_initial_browse_path(char **dst)
 {
 	char *base = NULL;
+#ifdef __ANDROID__
+	static const char activity_class_name[] = "com/retrodev/blastem/BlastEmActivity";
+	static const char get_rom_path_name[] = "getRomPath";
+	JNIEnv *env = SDL_AndroidGetJNIEnv();
+	jclass act_class = (*env)->FindClass(env, activity_class_name);
+	if (!act_class) {
+		fatal_error("Failed to find activity class %s\n", activity_class_name);
+	}
+	jmethodID meth = (*env)->GetMethodID(env, act_class, get_rom_path_name, "()Ljava/lang/String;");
+	if (!meth) {
+		fatal_error("Failed to find method %s\n", get_rom_path_name);
+	}
+	jobject activity = SDL_AndroidGetActivity();
+	jobject ret = (*env)->CallObjectMethod(env, activity, meth);
+	char *res = NULL;
+	if (ret) {
+		const char*utf = (*env)->GetStringUTFChars(env, (jstring)ret, NULL);
+		jsize len = (*env)->GetStringUTFLength(env, (jstring)ret);
+		res = calloc(len + 1, 1);
+		memcpy(res, utf, len);
+		debug_message("Got initial browse path: %s\n", res);
+		(*env)->ReleaseStringUTFChars(env, (jstring)ret, utf);
+		(*env)->DeleteLocalRef(env, ret);
+	}
+	
+	(*env)->DeleteLocalRef(env, activity);
+	if (res) {
+		*dst = res;
+		return 1;
+	}
+	return 0;
+#else
 	char *remember_path = tern_find_path(config, "ui\0remember_path\0", TVAL_PTR).ptrval;
 	if (!remember_path || !strcmp("on", remember_path)) {
 		char *pathfname = sticky_path_path();
@@ -104,8 +138,10 @@
 	if (!base) {
 		base = tern_find_path(config, "ui\0initial_path\0", TVAL_PTR).ptrval;
 	}
+#endif
 	if (!base){
 #ifdef __ANDROID__
+		
 		base = get_external_storage_path();
 #else
 		base = "$HOME";
@@ -116,6 +152,7 @@
 	*dst = replace_vars(base, vars, 1);
 	free(base);
 	tern_free(vars);
+	return 1;
 }
 
 char *path_append(const char *base, const char *suffix)
--- a/paths.h	Wed Mar 26 01:20:09 2025 -0700
+++ b/paths.h	Wed Mar 26 01:20:55 2025 -0700
@@ -1,7 +1,8 @@
 #ifndef PATHS_H_
 #define PATHS_H_
+#include <stdint.h>
 
-void get_initial_browse_path(char **dst);
+uint8_t get_initial_browse_path(char **dst);
 char *path_append(const char *base, const char *suffix);
 char *path_current_dir(void);
 
--- a/system.c	Wed Mar 26 01:20:09 2025 -0700
+++ b/system.c	Wed Mar 26 01:20:55 2025 -0700
@@ -18,7 +18,11 @@
 
 #ifdef DISABLE_ZLIB
 #define ROMFILE FILE*
+#ifdef __ANDROID__
+#define romopen fopen_wrapper
+#else
 #define romopen fopen
+#endif
 #define romread fread
 #define romseek fseek
 #define romgetc fgetc
@@ -26,7 +30,11 @@
 #else
 #include "zlib/zlib.h"
 #define ROMFILE gzFile
+#ifdef __ANDROID__
+#define romopen gzopen_wrapper
+#else
 #define romopen gzopen
+#endif
 #define romread gzfread
 #define romseek gzseek
 #define romgetc gzgetc
--- a/terminal.c	Wed Mar 26 01:20:09 2025 -0700
+++ b/terminal.c	Wed Mar 26 01:20:55 2025 -0700
@@ -26,7 +26,7 @@
 
 void init_terminal()
 {
-#ifndef IS_LIB
+#if !defined(IS_LIB) && !defined(__ANDROID__)
 	if (!init_done) {
 		if (!(isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))) {
 #ifndef __APPLE__
@@ -40,7 +40,7 @@
 			mkfifo(INPUT_PATH, 0666);
 			mkfifo(OUTPUT_PATH, 0666);
 
-			//close existing file descriptors
+			//close existing file descriptorsbundled_file_path
 			close(STDIN_FILENO);
 			close(STDOUT_FILENO);
 			close(STDERR_FILENO);
--- a/util.c	Wed Mar 26 01:20:09 2025 -0700
+++ b/util.c	Wed Mar 26 01:20:55 2025 -0700
@@ -11,6 +11,8 @@
 
 #ifdef __ANDROID__
 #include <android/log.h>
+#include <SDL_system.h>
+#include <jni.h>
 #define info_puts(msg) __android_log_write(ANDROID_LOG_INFO, "BlastEm", msg)
 #define warning_puts(msg) __android_log_write(ANDROID_LOG_WARN, "BlastEm", msg)
 #define fatal_puts(msg) __android_log_write(ANDROID_LOG_FATAL, "BlastEm", msg)
@@ -866,6 +868,54 @@
 
 dir_entry *get_dir_list(char *path, size_t *numret)
 {
+#ifdef __ANDROID__
+	debug_message("get_dir_list(%s)\n", path);
+	if (startswith(path, "content://")) {
+		static const char activity_class_name[] = "com/retrodev/blastem/BlastEmActivity";
+		static const char read_uri_dir_name[] = "readUriDir";
+		JNIEnv *env = SDL_AndroidGetJNIEnv();
+		jclass act_class = (*env)->FindClass(env, activity_class_name);
+		if (!act_class) {
+			fatal_error("Failed to find activity class %s\n", activity_class_name);
+		}
+		jmethodID meth = (*env)->GetMethodID(env, act_class, read_uri_dir_name, "(Ljava/lang/String;)[Ljava/lang/String;");
+		if (!meth) {
+			fatal_error("Failed to find method %s\n", read_uri_dir_name);
+		}
+		debug_message("get_dir_list(%s) using Storage Access Framework\n", path);
+		jstring jpath = (*env)->NewStringUTF(env, path);
+		jobject activity = SDL_AndroidGetActivity();
+		jobject ret = (*env)->CallObjectMethod(env, activity, meth, jpath);
+		dir_entry *res = NULL;
+		if (ret) {
+			jsize num = (*env)->GetArrayLength(env, ret);
+			if (numret) {
+				*numret = num;
+			}
+			res = calloc(num, sizeof(dir_entry));
+			for (jsize i = 0; i < num; i++)
+			{
+				jstring entry = (*env)->GetObjectArrayElement(env, ret, i);
+				char const *tmp = (*env)->GetStringUTFChars(env, entry, NULL);
+				jsize len = (*env)->GetStringUTFLength(env, entry);
+				res[i].name = calloc(len + 1, 1);
+				res[i].is_dir = tmp[len-1] == '/';
+				memcpy(res[i].name, tmp, res[i].is_dir ? len -1 : len);
+				(*env)->ReleaseStringUTFChars(env, entry, tmp);
+			}
+			(*env)->DeleteLocalRef(env, ret);
+		}
+		
+		(*env)->DeleteLocalRef(env, activity);
+		if (!res) {
+			if (numret) {
+				*numret = 0;
+			}
+			return NULL;
+		}
+		return res;
+	}
+#endif
 	DIR *d = opendir(path);
 	if (!d) {
 		if (numret) {
@@ -1004,7 +1054,60 @@
 	SDL_RWclose(rw);
 	return ret;
 }
+
+static int open_uri(const char *path, const char *mode)
+{
+	static const char activity_class_name[] = "com/retrodev/blastem/BlastEmActivity";
+	static const char open_uri_as_fd_name[] = "openUriAsFd";
+	JNIEnv *env = SDL_AndroidGetJNIEnv();
+	jclass act_class = (*env)->FindClass(env, activity_class_name);
+	if (!act_class) {
+		fatal_error("Failed to find activity class %s\n", activity_class_name);
+	}
+	jmethodID meth = (*env)->GetMethodID(env, act_class, open_uri_as_fd_name, "(Ljava/lang/String;Ljava/lang/String;)I");
+	if (!meth) {
+		fatal_error("Failed to find method %s\n", open_uri_as_fd_name);
+	}
+	jobject activity = SDL_AndroidGetActivity();
+	jstring jpath = (*env)->NewStringUTF(env, path);
+	jstring jmode = (*env)->NewStringUTF(env, mode);
+	int fd = (*env)->CallIntMethod(env, activity, meth, jpath, jmode);
+	(*env)->DeleteLocalRef(env, activity);
+	return fd;
+}
+
+FILE* fopen_wrapper(const char *path, const char *mode)
+{
+	if (startswith(path, "content://")) {
+		debug_message("fopen_wrapper(%s, %s) - Using Storage Access Framework\n", path, mode);
+		int fd = open_uri(path, mode);
+		if (!fd) {
+			return NULL;
+		}
+		return fdopen(fd, mode);
+	} else {
+		debug_message("fopen_wrapper(%s, %s) - Norma fopen\n", path, mode);
+		return fopen(path, mode);
+	}
+}
+
+#ifndef DISABLE_ZLIB
+gzFile gzopen_wrapper(const char *path, const char *mode)
+{
+	if (startswith(path, "content://")) {
+		debug_message("gzopen_wrapper(%s, %s) - Using Storage Access Framework\n", path, mode);
+		int fd = open_uri(path, mode);
+		if (!fd) {
+			return NULL;
+		}
+		return gzdopen(fd, mode);
+	} else {
+		debug_message("fopen_wrapper(%s, %s) - Norma gzopen\n", path, mode);
+		return gzopen(path, mode);
+	}
+}
 #endif
+#endif // IS_LIB
 
 char const *get_config_dir()
 {
--- a/util.h	Wed Mar 26 01:20:09 2025 -0700
+++ b/util.h	Wed Mar 26 01:20:55 2025 -0700
@@ -106,5 +106,12 @@
 int socket_last_error(void);
 //Returns if the last socket error was EAGAIN/EWOULDBLOCK
 int socket_error_is_wouldblock(void);
+#if defined(__ANDROID__) && !defined(IS_LIB)
+FILE* fopen_wrapper(const char *path, const char *mode);
+#ifndef DISABLE_ZLIB
+#include "zlib/zlib.h"
+gzFile gzopen_wrapper(const char *path, const char *mode);
+#endif
+#endif
 
 #endif //UTIL_H_
--- a/zip.c	Wed Mar 26 01:20:09 2025 -0700
+++ b/zip.c	Wed Mar 26 01:20:55 2025 -0700
@@ -21,7 +21,11 @@
 
 zip_file *zip_open(const char *filename)
 {
+#ifdef __ANDROID__
+	FILE *f = fopen_wrapper(filename, "rb");
+#else
 	FILE *f = fopen(filename, "rb");
+#endif
 	if (!f) {
 		return NULL;
 	}