Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open input blocks and never completed #92

Open
Daniel63656 opened this issue Oct 29, 2024 · 7 comments
Open

Open input blocks and never completed #92

Daniel63656 opened this issue Oct 29, 2024 · 7 comments

Comments

@Daniel63656
Copy link

Daniel63656 commented Oct 29, 2024

I try to receive messages from a connected usb MIDI device.

val midiAccess = AndroidMidiAccess(this)
val midiInput = runBlocking {
     midiAccess.openInput(midiAccess.inputs.first().id)
 }
midiInput.setMessageReceivedListener(this@MainActivity)

However, the app becomes unresponsive whenever calling openInput, indicating that no input can be established. This happens on google pixel 7 (android-version 14). The code only continues (setting listener) as soon as I unplug the Midi cable again.
I know this is caused by runBlocking and this only helps to make clear that no input can be established.
I noticed this did not happen on my older Samsung galaxy S9 (same code).

After some digging I found that the issue is this line in the MidiDevice.java file:
FileDescriptor fd = mDeviceServer.openInputPort(token, portNumber);
Somehow calling openInputPort on the IMidiDeviceServer blocks until the cable is removed again.

@atsushieno
Copy link
Owner

Opening a device (either via ktmidi or Android MIDI API directly) involves Service connection that most likely involves main thread (it might depend on how Android MIDI service is implemented, but anything that involves Service usually does), and you are most likely blocking the main thread. If you try to invoke openInput() on a background thread it may work.

@Daniel63656
Copy link
Author

If I do it in another thread then simply no connection is established until the cable is removed. I only switched to main thread to see what's going on.
This is likely caused by a permission problem as it works on an older phone. Newer android versions need the user to permit USB connection for each device that is plugged in think.

@atsushieno
Copy link
Owner

If you are trying to connect to a MIDI device that needs permission, you are supposed to provide enough permission (manage permission requests and approvals).

When we just deal with MIDI inputs, there is no required permission in MIDI API itself, like this app does not need any: https://github.com/atsushieno/resident-midi-keyboard/blob/main/app/src/main/AndroidManifest.xml

@Daniel63656
Copy link
Author

Daniel63656 commented Oct 31, 2024 via email

@atsushieno
Copy link
Owner

Yes, please let me know if it works or not. I would make sure that you are not trying to block the main thread while you are trying to (indirectly) show the permission approval dialog, as (IF that's still the case) the permission dialog also needs the main thread.

@Daniel63656
Copy link
Author

Hm I tried everything, opening from CoroutineScope and only until permission is granted for the device like so

 val usbReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                Log.d("USB", "Received callback")
                if (ACTION_USB_PERMISSION == intent.action) {
                    synchronized(this) {
                        val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
                        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                            Log.d("USB", "permission granted for device $device")
                            Toast.makeText(this@MainActivity, "CONNECTION ESTABLISHED!", Toast.LENGTH_SHORT).show()
                            CoroutineScope(Dispatchers.IO).launch {
                                val i = midiAccess.openInput("USB2.0-MIDI_2_0_1")
                                i.setMessageReceivedListener(this@MainActivity)
                            }
                        } else {
                            Log.d("USB", "permission denied for device $device")
                        }
                    }
                }
            }
        }

Still same behavior: Only if cable is removed, the receiver is connected.

@atsushieno
Copy link
Owner

Without any code I can examine, I can at best suggest some tips to debug your app issue: run it on Android Studio in debugging mode, then interrupt (break) while you are waiting for the openInput(), and check all those running threads on the debugger. There will be some blocking operation that is waiting for something that is waiting for some action that you think that should be already dispatched.
MIDI connections are controlled by MidiManager, the client to the system internal MIDI Service, which usually requires main thread not blocked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants