[Scummvm-git-logs] scummvm master -> d6df7db11430100d70eaa0628a36a54c2e3a103d

lephilousophe noreply at scummvm.org
Sat Sep 5 22:03:17 UTC 2026


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
d6df7db114 ANDROID: Add MIDI driver support


Commit: d6df7db11430100d70eaa0628a36a54c2e3a103d
    https://github.com/scummvm/scummvm/commit/d6df7db11430100d70eaa0628a36a54c2e3a103d
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-06T00:03:13+02:00

Commit Message:
ANDROID: Add MIDI driver support

BLE devices need an helper application to make them known.

Changed paths:
  A backends/midi/android.cpp
  A backends/platform/android/org/scummvm/scummvm/MidiPort.java
    backends/module.mk
    backends/platform/android/jni-android.cpp
    backends/platform/android/jni-android.h
    backends/platform/android/org/scummvm/scummvm/ScummVM.java
    backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
    base/plugins.cpp
    po/POTFILES


diff --git a/backends/midi/android.cpp b/backends/midi/android.cpp
new file mode 100644
index 00000000000..fc28471a719
--- /dev/null
+++ b/backends/midi/android.cpp
@@ -0,0 +1,299 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Allow use of stuff in <time.h> and abort()
+#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
+#define FORBIDDEN_SYMBOL_EXCEPTION_abort
+
+// Disable printf override in common/forbidden.h to avoid
+// clashes with log.h from the Android SDK.
+// That header file uses
+//   __attribute__ ((format(printf, 3, 4)))
+// which gets messed up by our override mechanism; this could
+// be avoided by either changing the Android SDK to use the equally
+// legal and valid
+//   __attribute__ ((format(__printf__, 3, 4)))
+// or by refining our printf override to use a varadic macro
+// (which then wouldn't be portable, though).
+// Anyway, for now we just disable the printf override globally
+// for the Android port
+#define FORBIDDEN_SYMBOL_EXCEPTION_printf
+
+#include "common/scummsys.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "backends/platform/android/android.h"
+#include "backends/platform/android/jni-android.h"
+
+#include "audio/musicplugin.h"
+#include "audio/mpu401.h"
+
+#include "common/translation.h"
+
+#include <dlfcn.h>
+#include <amidi/AMidi.h>
+
+class MidiDriver_Android : public MidiDriver_MPU401 {
+public:
+	MidiDriver_Android(jobject device, int32_t portId);
+	~MidiDriver_Android();
+	int open() override;
+	bool isOpen() const override { return _inPort; }
+	void close() override;
+	void send(uint32 b) override;
+	void sysEx(const byte *msg, uint16 length) override;
+
+private:
+	AMidiDevice *_device;
+	int32_t _portId;
+	AMidiInputPort *_inPort;
+
+
+	// We can't use NDK weak symbols because AMidi depends on libamidi.so to be loaded when our own library is
+	// but there is no weak library concept so if we reference it, it must be present.
+	static int amidi_loaded;
+	static media_status_t (*AMidiDevice_fromJava)(
+		JNIEnv *env, jobject midiDeviceObj, AMidiDevice **outDevicePtrPtr);
+	static media_status_t (*AMidiDevice_release)(const AMidiDevice *midiDevice);
+	static ssize_t (*AMidiDevice_getNumInputPorts)(const AMidiDevice *device);
+	static media_status_t (*AMidiInputPort_open)(const AMidiDevice *device, int32_t portNumber,
+		AMidiInputPort **outInputPortPtr);
+	static ssize_t (*AMidiInputPort_send)(const AMidiInputPort *inputPort, const uint8_t *buffer,
+                   size_t numBytes);
+	static void (*AMidiInputPort_close)(const AMidiInputPort *inputPort);
+};
+
+int MidiDriver_Android::amidi_loaded = 0;
+media_status_t (*MidiDriver_Android::AMidiDevice_fromJava)(
+	JNIEnv *env, jobject midiDeviceObj, AMidiDevice **outDevicePtrPtr) = nullptr;
+media_status_t (*MidiDriver_Android::AMidiDevice_release)(const AMidiDevice *midiDevice) = nullptr;
+ssize_t (*MidiDriver_Android::AMidiDevice_getNumInputPorts)(const AMidiDevice *device) = nullptr;
+media_status_t (*MidiDriver_Android::AMidiInputPort_open)(const AMidiDevice *device, int32_t portNumber,
+	AMidiInputPort **outInputPortPtr) = nullptr;
+ssize_t (*MidiDriver_Android::AMidiInputPort_send)(const AMidiInputPort *inputPort, const uint8_t *buffer,
+	   size_t numBytes) = nullptr;
+void (*MidiDriver_Android::AMidiInputPort_close)(const AMidiInputPort *inputPort) = nullptr;
+
+MidiDriver_Android::MidiDriver_Android(jobject device, int32_t portId)
+	: _device(nullptr), _portId(portId), _inPort(nullptr) {
+	if (!device) {
+		// Device couldn't be open in Android side: create a bogus device
+		return;
+	}
+
+	if (!amidi_loaded) {
+		// Lazy load the AMidi functions
+		void *amidi = dlopen("libamidi.so", RTLD_NOW | RTLD_LOCAL);
+		if (!amidi) {
+			warning("MidiDriver_Android can't load AMidi");
+			amidi_loaded = -1;
+		}
+
+#define LOAD_FUNC(n, T) \
+	if (!amidi_loaded && !(MidiDriver_Android::n = (T)dlsym(amidi, #n))) { \
+		amidi_loaded = -1; \
+		warning("MidiDriver_Android can't find AMidi function %s", #n); \
+	}
+
+		LOAD_FUNC(AMidiDevice_fromJava, media_status_t (*)(JNIEnv *, jobject, AMidiDevice **));
+		LOAD_FUNC(AMidiDevice_release, media_status_t (*)(const AMidiDevice *));
+		LOAD_FUNC(AMidiDevice_getNumInputPorts, ssize_t (*)(const AMidiDevice *));
+		LOAD_FUNC(AMidiInputPort_open, media_status_t (*)(const AMidiDevice *, int32_t, AMidiInputPort **));
+		LOAD_FUNC(AMidiInputPort_send, ssize_t (*)(const AMidiInputPort *, const uint8_t *, size_t));
+		LOAD_FUNC(AMidiInputPort_close, void (*)(const AMidiInputPort *));
+
+		if (!amidi_loaded) {
+			amidi_loaded = 1;
+		}
+
+	}
+	if (amidi_loaded != 1) {
+		// AMidi loading failed: create a bogus device
+		return;
+	}
+
+	JNIEnv *env = JNI::getEnv();
+
+	media_status_t status = MidiDriver_Android::AMidiDevice_fromJava(env, device, &_device);
+	env->DeleteLocalRef(device);
+
+	if (status != AMEDIA_OK) {
+		warning("Can't open MIDI device: %d", status);
+		_device = nullptr;
+	}
+}
+
+MidiDriver_Android::~MidiDriver_Android() {
+	if (_inPort) {
+		MidiDriver_Android::AMidiInputPort_close(_inPort);
+		_inPort = nullptr;
+	}
+	if (_device) {
+		MidiDriver_Android::AMidiDevice_release(_device);
+		_device = nullptr;
+	}
+}
+
+int MidiDriver_Android::open() {
+	if (!_device) {
+		return MERR_DEVICE_NOT_AVAILABLE;
+	}
+
+	if (isOpen()) {
+		return MERR_ALREADY_OPEN;
+	}
+
+	ssize_t inPorts = MidiDriver_Android::AMidiDevice_getNumInputPorts(_device);
+	if (inPorts < 0) {
+		warning("Can't get MIDI device input ports: %d", (int)inPorts);
+		return MERR_DEVICE_NOT_AVAILABLE;
+	}
+
+	if (_portId >= inPorts) {
+		return MERR_DEVICE_NOT_AVAILABLE;
+	}
+
+	media_status_t status = MidiDriver_Android::AMidiInputPort_open(_device, _portId, &_inPort);
+	if (status != AMEDIA_OK) {
+		g_system->displayMessageOnOSD(_("Can't connect MIDI port: no sound will be produced"));
+		_inPort = nullptr;
+		return MERR_CANNOT_CONNECT;
+	}
+
+	return 0;
+}
+
+void MidiDriver_Android::close() {
+	MidiDriver_MPU401::close();
+
+	if (_inPort) {
+		MidiDriver_Android::AMidiInputPort_close(_inPort);
+		_inPort = nullptr;
+	}
+}
+
+void MidiDriver_Android::send(uint32 b) {
+	assert(isOpen());
+
+	midiDriverCommonSend(b);
+
+	// Extract the MIDI data
+	byte data[3] = {
+		static_cast<byte>(b & 0x000000FF), // status byte
+		static_cast<byte>((b & 0x0000FF00) >> 8), // first byte
+		static_cast<byte>((b & 0x00FF0000) >> 16), // second byte
+	};
+
+	size_t data_length;
+
+	// Compute the correct length of the MIDI command. This is important,
+	// else things may screw up badly...
+	switch (data[0] & 0xF0) {
+	case 0x80:	// Note Off
+	case 0x90:	// Note On
+	case 0xA0:	// Polyphonic Aftertouch
+	case 0xB0:	// Controller Change
+	case 0xE0:	// Pitch Bending
+		data_length = 3;
+		break;
+	case 0xC0:	// Programm Change
+	case 0xD0:	// Monophonic Aftertouch
+		data_length = 2;
+		break;
+	default:
+		warning("Android driver encountered unsupported status byte: 0x%02x", data[0]);
+		data_length = 3;
+		break;
+	}
+
+	MidiDriver_Android::AMidiInputPort_send(_inPort, data, data_length);
+}
+
+void MidiDriver_Android::sysEx(const byte *msg, uint16 length) {
+	assert(isOpen());
+
+	unsigned char buf[270];
+
+	assert(length + 2 <= ARRAYSIZE(buf));
+
+	midiDriverCommonSysEx(msg, length);
+
+	// Add SysEx frame
+	buf[0] = 0xF0;
+	memcpy(buf + 1, msg, length);
+	buf[length + 1] = 0xF7;
+
+	MidiDriver_Android::AMidiInputPort_send(_inPort, buf, length + 2);
+}
+
+
+// Plugin interface
+
+class AndroidMusicPlugin : public MusicPluginObject {
+public:
+	const char *getName() const {
+		return "Android";
+	}
+
+	const char *getId() const {
+		return "android";
+	}
+
+	MusicDevices getDevices() const;
+	Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
+};
+
+MusicDevices AndroidMusicPlugin::getDevices() const {
+	MusicDevices devices;
+	Common::Array<Common::String> names = JNI::getMIDIDevices();
+	for (uint i = 0 ; i < names.size(); i++) {
+		devices.push_back(MusicDevice(this, names[i], MT_GM)); //Assume GM here
+	}
+	return devices;
+}
+
+Common::Error AndroidMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle device) const {
+	Common::Array<Common::String> names = JNI::getMIDIDevices();
+
+	jobject midiDevice = nullptr;
+	int32_t port = 0;
+
+	for (uint i = 0 ; i < names.size(); i++) {
+		MusicDevice md(this, names[i], MT_GM);
+		if (md.getHandle() == device) {
+			midiDevice = JNI::openMIDIDevice(i, &port);
+			break;
+		}
+	}
+	// Always return a driver even if the device doesn't exist: engines don't handle the error
+	// We will fail at opening
+	*mididriver = new MidiDriver_Android(midiDevice, port);
+	return Common::kNoError;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(ANDROID)
+	//REGISTER_PLUGIN_DYNAMIC(ANDROID, PLUGIN_TYPE_MUSIC, AndroidMusicPlugin);
+//#else
+	REGISTER_PLUGIN_STATIC(ANDROID, PLUGIN_TYPE_MUSIC, AndroidMusicPlugin);
+//#endif
diff --git a/backends/module.mk b/backends/module.mk
index b3b8a5a0028..02a21d2f855 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -355,6 +355,7 @@ MODULE_OBJS += \
 	fs/android/android-posix-fs.o \
 	fs/android/android-saf-fs.o \
 	graphics/android/android-graphics.o \
+	midi/android.o \
 	mixer/android/android-mixer.o \
 	mutex/pthread/pthread-mutex.o \
 	networking/basic/android/jni.o \
diff --git a/backends/platform/android/jni-android.cpp b/backends/platform/android/jni-android.cpp
index a954f2c7363..6378c82d0b1 100644
--- a/backends/platform/android/jni-android.cpp
+++ b/backends/platform/android/jni-android.cpp
@@ -103,6 +103,8 @@ jmethodID JNI::_MID_getScummVMLogPath;
 jmethodID JNI::_MID_setCurrentGame = 0;
 jmethodID JNI::_MID_notifyHTTPService = 0;
 jmethodID JNI::_MID_getTTSManager = 0;
+jmethodID JNI::_MID_getMIDIDevices = 0;
+jmethodID JNI::_MID_openMIDIDevice = 0;
 jmethodID JNI::_MID_getSysArchives = 0;
 jmethodID JNI::_MID_getAllStorageLocations = 0;
 jmethodID JNI::_MID_initSurface = 0;
@@ -612,6 +614,64 @@ jobject JNI::getTTSManager() {
 	return ttsManager;
 }
 
+Common::Array<Common::String> JNI::getMIDIDevices() {
+	Common::Array<Common::String> res;
+
+	JNIEnv *env = JNI::getEnv();
+
+	jobjectArray array =
+		(jobjectArray)env->CallObjectMethod(_jobj, _MID_getMIDIDevices);
+
+	if (env->ExceptionCheck()) {
+		LOGE("Error getting MIDI devices");
+
+		env->ExceptionDescribe();
+		env->ExceptionClear();
+
+		return res;
+	}
+
+	jsize size = env->GetArrayLength(array);
+	for (jsize i = 0; i < size; ++i) {
+		jstring midi_obj = (jstring)env->GetObjectArrayElement(array, i);
+		const char *midi = env->GetStringUTFChars(midi_obj, 0);
+
+		if (midi) {
+			res.push_back(midi);
+			env->ReleaseStringUTFChars(midi_obj, midi);
+		}
+
+		env->DeleteLocalRef(midi_obj);
+	}
+
+	env->DeleteLocalRef(array);
+	return res;
+}
+
+jobject JNI::openMIDIDevice(int device, int32_t *portId) {
+	Common::Array<Common::String> res;
+
+	JNIEnv *env = JNI::getEnv();
+
+	jintArray portIdArray = env->NewIntArray(1);
+
+	jobject deviceObj = env->CallObjectMethod(_jobj, _MID_openMIDIDevice, device, portIdArray);
+
+	if (env->ExceptionCheck()) {
+		LOGE("Error opening MIDI device");
+
+		env->ExceptionDescribe();
+		env->ExceptionClear();
+
+		return nullptr;
+	}
+
+	env->GetIntArrayRegion(portIdArray, 0, 1, portId);
+	env->DeleteLocalRef(portIdArray);
+
+	return deviceObj;
+}
+
 // The following adds assets folder to search set.
 // However searching and retrieving from "assets" on Android this is slow
 // so we also make sure to add the base directory, with a higher priority
@@ -756,6 +816,8 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
 	FIND_METHOD(, setCurrentGame, "(Ljava/lang/String;)V");
 	FIND_METHOD(, notifyHTTPService, "(IZ)V");
 	FIND_METHOD(, getTTSManager, "()Lorg/scummvm/scummvm/TextToSpeechManager;");
+	FIND_METHOD(, getMIDIDevices, "()[Ljava/lang/String;");
+	FIND_METHOD(, openMIDIDevice, "(I[I)Landroid/media/midi/MidiDevice;");
 	FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
 	FIND_METHOD(, getAllStorageLocations, "()[Ljava/lang/String;");
 	FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
diff --git a/backends/platform/android/jni-android.h b/backends/platform/android/jni-android.h
index dc850d754ab..5763d610753 100644
--- a/backends/platform/android/jni-android.h
+++ b/backends/platform/android/jni-android.h
@@ -102,6 +102,8 @@ public:
 	static void setCurrentGame(const Common::String &target);
 	static void notifyHTTPService(int localPort, bool minimal);
 	static jobject getTTSManager();
+	static Common::Array<Common::String> getMIDIDevices();
+	static jobject openMIDIDevice(int deviceId, int *port);
 
 	static inline bool haveSurface();
 	static inline bool swapBuffers();
@@ -161,6 +163,8 @@ private:
 	static jmethodID _MID_setCurrentGame;
 	static jmethodID _MID_notifyHTTPService;
 	static jmethodID _MID_getTTSManager;
+	static jmethodID _MID_getMIDIDevices;
+	static jmethodID _MID_openMIDIDevice;
 	static jmethodID _MID_getSysArchives;
 	static jmethodID _MID_getAllStorageLocations;
 	static jmethodID _MID_initSurface;
diff --git a/backends/platform/android/org/scummvm/scummvm/MidiPort.java b/backends/platform/android/org/scummvm/scummvm/MidiPort.java
new file mode 100644
index 00000000000..e503cceb95d
--- /dev/null
+++ b/backends/platform/android/org/scummvm/scummvm/MidiPort.java
@@ -0,0 +1,152 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.scummvm.scummvm;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+// MIDI is available from Marshmallow but not exposed by NDK before Quince Tart
+ at RequiresApi(Build.VERSION_CODES.Q)
+class MidiPort {
+    private static class MidiDeviceOpenedListener implements MidiManager.OnDeviceOpenedListener {
+        final Object synchronizer = new Object();
+        MidiDevice midiDevice = null;
+
+        @Override
+        public void onDeviceOpened(MidiDevice midiDevice) {
+            synchronized (synchronizer) {
+                this.midiDevice = midiDevice;
+                synchronizer.notify();
+            }
+        }
+    }
+
+    private final MidiDeviceInfo device;
+    private final MidiDeviceInfo.PortInfo port;
+    private String name;
+
+    private MidiPort(MidiDeviceInfo device, MidiDeviceInfo.PortInfo port) {
+        this.device = device;
+        this.port = port;
+    }
+
+    @NonNull
+    public static ArrayList<MidiPort> getMidiPorts(@NonNull Context context) {
+        MidiManager manager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+        MidiDeviceInfo[] devices;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            Set<MidiDeviceInfo> infosSet = manager.getDevicesForTransport(MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
+            devices = infosSet.toArray(new MidiDeviceInfo[]{});
+        } else {
+            // This useless intermediate variable is here to allow attaching the annotation
+            @SuppressWarnings({"deprecation", "RedundantSuppression"})
+            MidiDeviceInfo[] devices_ = manager.getDevices();
+            devices = devices_;
+        }
+        ArrayList<MidiPort> ports = new ArrayList<>();
+        for (MidiDeviceInfo device : devices) {
+            for (MidiDeviceInfo.PortInfo port : device.getPorts()) {
+                if (port.getType() != MidiDeviceInfo.PortInfo.TYPE_INPUT) {
+                    continue;
+                }
+                ports.add(new MidiPort(device, port));
+            }
+        }
+        ports.sort((MidiPort l, MidiPort r) -> l.getName().compareTo(r.getName()));
+        return ports;
+    }
+
+    @NonNull
+    public String getName() {
+        if (name != null) {
+            return name;
+        }
+
+        Bundle props = device.getProperties();
+        String name = props.getString(MidiDeviceInfo.PROPERTY_NAME);
+        final String serial = props.getString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER);
+        if (serial != null && !serial.isEmpty()) {
+            name += " (" + serial + ")";
+        }
+        name += " - ";
+        String portName = port.getName();
+        if (portName != null && !portName.isEmpty()) {
+            name += portName;
+        } else {
+            int portNum = port.getPortNumber();
+            name += portNum;
+        }
+        this.name = name;
+        return name;
+    }
+
+    public MidiDevice open(@NonNull Context context, @NonNull int[] portId) {
+        MidiManager manager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+
+        MidiDeviceOpenedListener listener = new MidiDeviceOpenedListener();
+        HandlerThread thread = new HandlerThread("MIDI Device Handler");
+        thread.start();
+        Handler handler = new Handler(thread.getLooper());
+
+        BluetoothDevice ble;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            ble = device.getProperties().getParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE, BluetoothDevice.class);
+        } else {
+            @SuppressWarnings({"deprecation", "RedundantSuppression"})
+            Object ble_ = device.getProperties().getParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE);
+            ble = (BluetoothDevice) ble_;
+        }
+        synchronized (listener.synchronizer) {
+            if (ble != null) {
+                manager.openBluetoothDevice(ble, listener, handler);
+            } else {
+                manager.openDevice(device, listener, handler);
+            }
+
+            try {
+                listener.synchronizer.wait();
+            } catch (InterruptedException e) {
+                Log.d(ScummVM.LOG_TAG, "Warning: interrupted while waiting for MIDI device");
+                thread.quit();
+                return null;
+            }
+        }
+
+        thread.quit();
+        portId[0] = port.getPortNumber();
+        return listener.midiDevice;
+    }
+}
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVM.java b/backends/platform/android/org/scummvm/scummvm/ScummVM.java
index 02bff3af178..37c223f04d7 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVM.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVM.java
@@ -23,6 +23,7 @@ package org.scummvm.scummvm;
 
 import android.content.res.AssetManager;
 import android.graphics.PixelFormat;
+import android.media.midi.MidiDevice;
 import android.util.Log;
 import android.view.SurfaceHolder;
 
@@ -131,6 +132,10 @@ public abstract class ScummVM implements SurfaceHolder.Callback,
 	/** @noinspection unused */ @Keep
 	abstract protected TextToSpeechManager getTTSManager();
 	/** @noinspection unused */ @Keep
+	abstract protected String[] getMIDIDevices();
+	/** @noinspection unused */ @Keep
+	abstract protected MidiDevice openMIDIDevice(int device, int[] portId);
+	/** @noinspection unused */ @Keep
 	abstract protected String[] getSysArchives();
 	/** @noinspection unused */ @Keep
 	abstract protected String[] getAllStorageLocations();
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
index 740cb73a090..9ba500428f1 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
@@ -43,6 +43,7 @@ import android.hardware.usb.UsbManager;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioTrack;
+import android.media.midi.MidiDevice;
 import android.net.ConnectivityManager;
 import android.net.Uri;
 import android.net.nsd.NsdManager;
@@ -87,6 +88,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.HashSet;
@@ -973,6 +975,37 @@ public class ScummVMActivity extends Activity {
 			return _tts;
 		}
 
+		@Override
+		protected String[] getMIDIDevices() {
+			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+				// No MIDI support before Marshmallow and no NDK support before Quince Tart
+				return new String[]{};
+			}
+
+			ArrayList<MidiPort> ports = MidiPort.getMidiPorts(ScummVMActivity.this);
+			String[] devices = new String[ports.size()];
+			for (int i = 0; i < devices.length; i++) {
+				devices[i] = ports.get(i).getName();
+			}
+			return devices;
+		}
+
+		@Override
+		protected MidiDevice openMIDIDevice(int device, int[] portId) {
+			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+				// No MIDI support before Marshmallow and no NDK support before Quince Tart
+				return null;
+			}
+
+			ArrayList<MidiPort> ports = MidiPort.getMidiPorts(ScummVMActivity.this);
+			if (device >= ports.size()) {
+				return null;
+			}
+			MidiPort portDevice = ports.get(device);
+
+			return portDevice.open(ScummVMActivity.this, portId);
+		}
+
 		@Override
 		protected String[] getSysArchives() {
 			File assetsDir = new File(_actualScummVMDataDir, "assets");
diff --git a/base/plugins.cpp b/base/plugins.cpp
index 93aaa7bcbfd..d6e11d086a6 100644
--- a/base/plugins.cpp
+++ b/base/plugins.cpp
@@ -128,6 +128,9 @@ public:
 		#if defined(RISCOS)
 		LINK_PLUGIN(RISCOS)
 		#endif
+		#if defined(ANDROID_BACKEND)
+		LINK_PLUGIN(ANDROID)
+		#endif
 		#if defined(MACOSX)
 		LINK_PLUGIN(COREAUDIO)
 		#endif
diff --git a/po/POTFILES b/po/POTFILES
index f43ba5ec61c..34056a85dde 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -96,6 +96,7 @@ backends/keymapper/hardware-input.cpp
 backends/keymapper/remap-widget.cpp
 backends/keymapper/virtual-mouse.cpp
 backends/midi/windows.cpp
+backends/midi/android.cpp
 backends/networking/sdl_net/handlers/createdirectoryhandler.cpp
 backends/networking/sdl_net/handlers/downloadfilehandler.cpp
 backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp




More information about the Scummvm-git-logs mailing list