Disconnecting/reconnecting Android MIDI devices causes app to hang

Hi Guys,

I’m writing an app that needs to handle multiple MIDI devices on Android in a robust way. I’ve found that if a device is unplugged, then reconnected to the same USB port, the whole app will hang until one or more MIDI devices is also disconnected, then spring back to life.

I’ve created a super simple test app that mimics the core of my main app. It just runs a timer, detects if there has been a change of midi device and opens ports to any devices present, and can replicate the hang every time.

void MainContentComponent::timerCallback()
{
    StringArray newInputList = MidiInput::getDevices();
    StringArray newOutputList = MidiOutput::getDevices();

    bool inputHasChanged = false;
    for (int i = 0; i < newInputList.size(); i++)
    {
        if (!inputList.contains(newInputList[i]))
        {
            inputHasChanged = true;
            break;
        }
    }

    for (int i = 0; i < inputList.size(); i++)
    {
        if (!newInputList.contains(inputList[i]))
        {
            inputHasChanged = true;
            break;
        }
    }

    bool outputHasChanged = false;
    for (int i = 0; i < newOutputList.size(); i++)
    {
        if (!outputList.contains(newOutputList[i]))
        {
            outputHasChanged = true;
            break;
        }
    }

    for (int i = 0; i < outputList.size(); i++)
    {
        if (!newOutputList.contains(outputList[i]))
        {
            outputHasChanged = true;
            break;
        }
    }

    if (inputList.size() != newInputList.size())
    {
        inputHasChanged = true;
    }

    if (outputList.size() != newOutputList.size())
    {
        outputHasChanged = true;
    }

    if (inputHasChanged)
    {
        inputLog += "--------------------------------\n";
        inputLog += "Input has changed: \n";

        for (int i = 0; i < midiInputs.size(); ++i)
        {
            midiInputs[i]->stop();
        }

        midiInputs.clearQuick(true);

        for (int i = 0; i < newInputList.size(); i++)
        {
            inputLog = inputLog + newInputList[i] + "\n";
            DBG("Opening Device: " + newInputList[i]);
            midiInputs.add(MidiInput::openDevice(i, this));

            if (midiInputs.getLast() == nullptr)
            {
                inputLog += "input device error: " + midiInputs[i]->getName() + "\n";
            }
        }

        inputList = newInputList;
        inputDisp.setText(inputLog);
        inputDisp.moveCaretToEnd();
    }

    if (outputHasChanged)
    {
        outputLog += "--------------------------------\n";
        outputLog += "Output has changed: \n";

        for (int i = 0; i < midiOutputs.size(); ++i)
        {
            if (midiOutputs[i] != nullptr)
            {
                midiOutputs[i]->stopBackgroundThread();
            }
            else
            {
                outputLog += "Output device error: " + midiOutputs[i]->getName() + "\n";
            }
        }
        midiOutputs.clearQuick(true);

        for (int i = 0; i < newOutputList.size(); i++)
        {
            outputLog = outputLog + newOutputList[i] + "\n";
            DBG("Opening Device: " + newOutputList[i]);
            midiOutputs.add(MidiOutput::openDevice(i));


            if (midiInputs.getLast() == nullptr)
            {
                inputLog += "Output device error: " + midiInputs[i]->getName() + "\n";
            }
            else
            {
                midiOutputs[i]->startBackgroundThread();
            }
        }
        outputList = newOutputList;
        outputDisp.setText(outputLog);
        outputDisp.moveCaretToEnd();
        
    }
}

I’m not getting anything useful from ADB, any input would be appreciated.

Hmmm, I tried your code on the Nexus 5X and connected 5 devices and disconnected them one by one. Seems to work on the Nexus 5X. Are you using the latest version of JUCE?

Hi Fabian, thanks for taking a look at this. I’m running Juce 5.0.0, Have tested on an Nvidea Shield tablet and a Oneplus 3T so far with the same results. They are both running Android 7 if that makes any difference?

Best way to get a hang/crash is to move along your devices one by one, disconnect the device, then reconnect it before disconnecting the next one.

Can you also try this with the MidiTest example app: JUCE/examples/MidiTest?

Sure, will test now.

Managed to replicate this with MidiTest.

  • Plugged in 3 MIDI devices
  • Enabled all inputs and outputs
  • Unplugged one of the devices
  • Reconnected the unplugged device after 5 seconds or so
  • App hangs until all other MIDI devices are removed, then returns to normal.

Some of the devices have more than one MIDI port if that makes any difference?

Any thoughts? Is there any more information I can give?

I have been trying this on a Nexus 5X via bluetooth and it works fine. For some reason the Nexus 5X that we have here doesn’t seem to like using a USB hub so I couldn’t test multiple usb devices. Can you re-produce your bug just using bluetooth midi?

Ok, afraid I don’t have more than one bluetooth midi device so can’t really test that.

From the reading i’ve done, USB midi on android uses the ALSA drivers for USB host mode, whereas bluetooth/inter-app doesn’t.

MIDI  |  Android Open Source Project
The implementation depends on ALSA for USB host mode and USB peripheral mode transports. ALSA is not used for the BLE and virtual transports.

Perhaps the problem occurs where juce is interfacing with ALSA?

We don’t interact with ALSA directly. We use the JAVA android MidiManager class.

I’ll keep digging around, in the mean time is there a chance you could give this a quick test with a USB hub?

Alternatively, could you give me some sort of test procedure that I can perform here and send you the results?

Hi @lukealpha,
I’ve been in investigating this bug for a few hours now and - although I’m sure there are a few bugs also in the JUCE code - it’s definitely triggering bugs that are very likely android system bugs. I’m in contact with one of the engineers at Google who wrote most of the android midi code to figure out exactly what’s going on.

I’ll let you know once I know more.