More robust USB MIDI keyboard handling

This patch implements better USB MIDI handling, including listening for
detach, and handling the case where it's connected while the activity is
active.
master
Raph Levien 11 years ago
parent 5a20308469
commit 9746d9626e
  1. 3
      android/AndroidManifest.xml
  2. 151
      android/src/com/levien/synthesizer/android/ui/PianoActivity2.java
  3. 93
      android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java

@ -14,7 +14,8 @@
<activity
android:name="com.levien.synthesizer.android.ui.PianoActivity2"
android:label="@string/app_name"
android:screenOrientation="landscape">
android:screenOrientation="landscape"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

@ -23,11 +23,12 @@ import java.util.HashMap;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.hardware.usb.UsbConstants;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.media.AudioManager;
@ -48,6 +49,7 @@ import android.widget.TextView;
import com.levien.synthesizer.R;
import com.levien.synthesizer.android.AndroidGlue;
import com.levien.synthesizer.android.stats.JitterStats;
import com.levien.synthesizer.android.usb.UsbMidiDevice;
import com.levien.synthesizer.android.widgets.knob.KnobListener;
import com.levien.synthesizer.android.widgets.knob.KnobView;
import com.levien.synthesizer.android.widgets.piano.PianoView;
@ -60,6 +62,7 @@ import com.levien.synthesizer.android.widgets.piano.PianoView;
public class PianoActivity2 extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d("synth", "activity onCreate");
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@ -125,9 +128,8 @@ public class PianoActivity2 extends Activity {
});
piano_.bindTo(androidGlue_);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
tryConnectUsb();
}
// This is now done in onResume
//tryConnectUsb();
final boolean doStats = false;
@ -178,93 +180,128 @@ public class PianoActivity2 extends Activity {
@Override
protected void onDestroy() {
Log.d("synth", "activity onDestroy");
androidGlue_.shutdown();
super.onDestroy();
}
@Override
protected void onPause() {
Log.d("synth", "activity onPause");
androidGlue_.setPlayState(false);
unregisterReceiver(usbReceiver_);
setMidiInterface(null, null);
super.onPause();
}
@Override
protected void onResume() {
Log.d("synth", "activity onResume");
androidGlue_.setPlayState(true);
tryConnectUsb();
super.onResume();
}
@Override
protected void onNewIntent(Intent intent) {
Log.d("synth", "activity onNewIntent " + intent);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private UsbEndpoint getInputEndpoint(UsbInterface usbIf) {
int nEndpoints = usbIf.getEndpointCount();
for (int i = 0; i < nEndpoints; i++) {
UsbEndpoint endpoint = usbIf.getEndpoint(i);
if (endpoint.getDirection() == UsbConstants.USB_DIR_IN &&
endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
return endpoint;
static private UsbInterface findMidiInterface(UsbDevice device) {
int count = device.getInterfaceCount();
for (int i = 0; i < count; i++) {
UsbInterface usbIf = device.getInterface(i);
if (usbIf.getInterfaceClass() == 1 && usbIf.getInterfaceSubclass() == 3) {
return usbIf;
}
}
return null;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private void startUsbThread(final UsbDeviceConnection connection, final UsbEndpoint endpoint) {
Thread thread = new Thread(new Runnable() {
public void run() {
byte[] buf = new byte[endpoint.getMaxPacketSize()];
while (true) {
int nBytes = connection.bulkTransfer(endpoint, buf, buf.length, 10000);
for (int i = 0; i < nBytes; i += 4) {
int codeIndexNumber = buf[i] & 0xf;
int payloadBytes = 0;
if (codeIndexNumber == 8 || codeIndexNumber == 9 || codeIndexNumber == 11 ||
codeIndexNumber == 14) {
payloadBytes = 3;
} else if (codeIndexNumber == 12) {
payloadBytes = 2;
}
if (payloadBytes > 0) {
byte[] newBuf = new byte[payloadBytes];
System.arraycopy(buf, i + 1, newBuf, 0, payloadBytes);
androidGlue_.onMessage(newBuf);
}
}
private boolean setMidiInterface(UsbDevice device, UsbInterface intf) {
if (usbMidiConnection_ != null) {
if (usbMidiDevice_ != null) {
usbMidiDevice_.stop();
usbMidiDevice_ = null;
}
if (usbMidiInterface_ != null) {
usbMidiConnection_.releaseInterface(usbMidiInterface_);
}
usbMidiConnection_.close();
usbMidiDeviceName_ = null;
usbMidiConnection_ = null;
}
if (device != null && intf != null) {
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbDeviceConnection connection = usbManager.openDevice(device);
if (connection != null) {
if (connection.claimInterface(intf, true)) {
usbMidiDeviceName_ = device.getDeviceName();
usbMidiConnection_ = connection;
usbMidiInterface_ = intf;
usbMidiDevice_ = new UsbMidiDevice(this, usbMidiConnection_, intf);
usbMidiDevice_.start();
return true;
} else {
Log.e("synth", "failed to claim USB interface");
connection.close();
}
} else {
Log.e("synth", "open device failed");
}
});
thread.start();
}
return false;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private void tryConnectUsb() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
return;
}
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
TextView label = (TextView)findViewById(R.id.status);
Log.i("synth", "USB device count=" + deviceList.size());
for (UsbDevice device : deviceList.values()) {
//label.setText("ic:" + device.getInterfaceCount());
Log.i("synth", "usb name=" + device.toString() + " #if=" + device.getInterfaceCount());
if (device.getInterfaceCount() < 2) {
continue;
UsbInterface usbIf = findMidiInterface(device);
if (setMidiInterface(device, usbIf)) {
break;
}
UsbInterface usbIf = device.getInterface(1);
if (usbIf.getInterfaceClass() != 1 || usbIf.getInterfaceSubclass() != 3) {
continue;
}
UsbDeviceConnection connection = usbManager.openDevice(device);
if (connection != null) {
connection.claimInterface(usbIf, true);
UsbEndpoint endpoint = getInputEndpoint(usbIf);
//label.setText(endpoint.toString());
Log.i("synth", "MIDI keyboard detected, starting USB thread");
startUsbThread(connection, endpoint);
} else {
if (label != null) {
Log.i("synth", "error opening USB MIDI device");
label.setText("error opening device");
}
IntentFilter filter = new IntentFilter();
// Attach intent is handled in manifest, don't want to get it twice.
//filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(usbReceiver_, filter);
}
BroadcastReceiver usbReceiver_ = new BroadcastReceiver() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
Log.i("synth", "broadcast receiver: attach");
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
UsbInterface intf = findMidiInterface(device);
if (intf != null) {
setMidiInterface(device, intf);
}
} else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
Log.i("synth", "broadcast receiver: detach");
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
String deviceName = device.getDeviceName();
if (usbMidiDeviceName_ != null && usbMidiDeviceName_.equals(deviceName)) {
setMidiInterface(null, null);
}
}
break;
}
};
public void sendMidiBytes(byte[] buf) {
// TODO: in future we'll want to reflect MIDI to UI (knobs turn, keys press)
androidGlue_.sendMidi(buf);
}
class AudioParams {
@ -301,4 +338,8 @@ public class PianoActivity2 extends Activity {
private Handler statusHandler_;
private Runnable statusRunnable_;
private JitterStats jitterStats_;
private UsbDeviceConnection usbMidiConnection_;
private UsbMidiDevice usbMidiDevice_;
private UsbInterface usbMidiInterface_;
private String usbMidiDeviceName_;
}

@ -0,0 +1,93 @@
// TODO copyright
// Class representing a USB MIDI keyboard
package com.levien.synthesizer.android.usb;
import android.annotation.TargetApi;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.os.Build;
import android.util.Log;
import com.levien.synthesizer.android.ui.PianoActivity2;
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class UsbMidiDevice {
private final PianoActivity2 mActivity;
private final UsbDeviceConnection mDeviceConnection;
private final UsbEndpoint mEndpoint;
private final WaiterThread mWaiterThread = new WaiterThread();
public UsbMidiDevice(PianoActivity2 activity, UsbDeviceConnection connection, UsbInterface intf) {
mActivity = activity;
mDeviceConnection = connection;
mEndpoint = getInputEndpoint(intf);
}
private UsbEndpoint getInputEndpoint(UsbInterface usbIf) {
Log.d("synth", "interface:" + usbIf.toString());
int nEndpoints = usbIf.getEndpointCount();
for (int i = 0; i < nEndpoints; i++) {
UsbEndpoint endpoint = usbIf.getEndpoint(i);
if (endpoint.getDirection() == UsbConstants.USB_DIR_IN &&
endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
return endpoint;
}
}
return null;
}
public void start() {
Log.d("synth", "midi USB waiter thread starting");
mWaiterThread.start();
}
public void stop() {
synchronized (mWaiterThread) {
mWaiterThread.mStop = true;
}
}
private class WaiterThread extends Thread {
public boolean mStop;
public void run() {
byte[] buf = new byte[mEndpoint.getMaxPacketSize()];
while (true) {
synchronized (this) {
if (mStop) {
Log.d("synth", "midi USB waiter thread shutting down");
return;
}
}
final int TIMEOUT = 1000;
int nBytes = mDeviceConnection.bulkTransfer(mEndpoint, buf, buf.length, TIMEOUT);
if (nBytes < 0) {
Log.e("synth", "bulkTransfer error " + nBytes);
// break;
}
for (int i = 0; i < nBytes; i += 4) {
int codeIndexNumber = buf[i] & 0xf;
int payloadBytes = 0;
if (codeIndexNumber == 8 || codeIndexNumber == 9 || codeIndexNumber == 11 ||
codeIndexNumber == 14) {
payloadBytes = 3;
} else if (codeIndexNumber == 12) {
payloadBytes = 2;
}
if (payloadBytes > 0) {
byte[] newBuf = new byte[payloadBytes];
System.arraycopy(buf, i + 1, newBuf, 0, payloadBytes);
Log.d("synth", "sending midi");
mActivity.sendMidiBytes(newBuf);
}
}
}
}
}
}
Loading…
Cancel
Save