Move USB device to SynthesizerService

This patch moves the connection to the USB MIDI keyboard from a single
activity to the SynthesizerService so that it can run while the user
switches between more than one activity.

Note that in the current state, there are problems when the device is
detached when the service isn't running.
master
Raph Levien 11 years ago
parent 9f1b753f81
commit a762514187
  1. 85
      android/src/com/levien/synthesizer/android/service/SynthesizerService.java
  2. 96
      android/src/com/levien/synthesizer/android/ui/PianoActivity2.java
  3. 24
      android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java

@ -23,8 +23,14 @@ import java.util.List;
import android.annotation.TargetApi;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Build;
@ -33,6 +39,7 @@ import android.util.Log;
import com.levien.synthesizer.R;
import com.levien.synthesizer.android.AndroidGlue;
import com.levien.synthesizer.android.usb.UsbMidiDevice;
import com.levien.synthesizer.core.midi.MidiListener;
import com.levien.synthesizer.core.model.composite.MultiChannelSynthesizer;
@ -91,6 +98,13 @@ public class SynthesizerService extends Service {
}
}
androidGlue_.setPlayState(true);
if (usbDevice_ != null && usbMidiConnection_ == null) {
connectUsbMidi(usbDevice_);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(usbReceiver_, filter);
}
}
/**
@ -99,6 +113,10 @@ public class SynthesizerService extends Service {
@Override
public void onDestroy() {
androidGlue_.setPlayState(false);
setMidiInterface(null, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
unregisterReceiver(usbReceiver_);
}
}
/**
@ -150,10 +168,77 @@ public class SynthesizerService extends Service {
params.bufferSize = Integer.parseInt(bs);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private boolean setMidiInterface(UsbDevice device, UsbInterface intf) {
if (usbMidiConnection_ != null) {
if (usbMidiDevice_ != null) {
usbMidiDevice_.stop();
usbMidiDevice_ = null;
}
if (usbMidiInterface_ != null) {
// Note: releasing the interface seems to trigger bugs, so
// based on experimentation just closing seems more robust
//usbMidiConnection_.releaseInterface(usbMidiInterface_);
}
usbMidiConnection_.close();
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)) {
usbDevice_ = device;
usbMidiConnection_ = connection;
usbMidiInterface_ = intf;
usbMidiDevice_ = new UsbMidiDevice(androidGlue_, usbMidiConnection_, intf);
usbMidiDevice_.start();
return true;
} else {
Log.e("synth", "failed to claim USB interface");
connection.close();
}
} else {
Log.e("synth", "open device failed");
}
}
return false;
}
// Handles both connect and disconnect actions
public boolean connectUsbMidi(UsbDevice device) {
UsbInterface intf = device != null ? UsbMidiDevice.findMidiInterface(device) : null;
Log.d("synth", "connecting USB");
boolean success = setMidiInterface(device, intf);
Log.d("synth", "connecting USB done");
usbDevice_ = success ? device : null;
return success;
}
private final BroadcastReceiver usbReceiver_ = new BroadcastReceiver() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public void onReceive(Context context, Intent intent) {
Log.d("synth", "service broadcast receiver got " + intent);
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device.equals(usbDevice_)) {
connectUsbMidi(null);
}
}
}
};
// Binder to use for Activities in this process.
private final IBinder binder_ = new LocalBinder();
private static AndroidGlue androidGlue_;
private static List<String> patchNames_;
// State for USB MIDI keyboard connection
private static UsbDevice usbDevice_;
private UsbDeviceConnection usbMidiConnection_;
private UsbMidiDevice usbMidiDevice_;
private UsbInterface usbMidiInterface_;
}

@ -117,16 +117,12 @@ public class PianoActivity2 extends Activity {
@Override
protected void onPause() {
Log.d("synth", "activity onPause");
setMidiInterface(null, null);
super.onPause();
}
@Override
protected void onResume() {
Log.d("synth", "activity onResume " + getIntent());
if (usbDevice_ != null && usbMidiConnection_ == null) {
connectUsbMidi(usbDevice_);
}
super.onResume();
}
@ -149,62 +145,6 @@ public class PianoActivity2 extends Activity {
connectUsbFromIntent(intent);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
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 boolean setMidiInterface(UsbDevice device, UsbInterface intf) {
if (usbMidiConnection_ != null) {
if (usbMidiDevice_ != null) {
usbMidiDevice_.stop();
usbMidiDevice_ = null;
}
if (usbMidiInterface_ != null) {
// Note: releasing the interface seems to trigger bugs, so
// based on experimentation just closing seems more robust
//usbMidiConnection_.releaseInterface(usbMidiInterface_);
}
usbMidiConnection_.close();
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)) {
usbDevice_ = device;
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");
}
}
return false;
}
private boolean connectUsbMidi(UsbDevice device) {
UsbInterface intf = device != null ? findMidiInterface(device) : null;
boolean success = setMidiInterface(device, intf);
usbDevice_ = success ? device : null;
return success;
}
// scan for MIDI devices on the USB bus
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private void scanUsbMidi() {
@ -212,11 +152,10 @@ public class PianoActivity2 extends Activity {
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
Log.i("synth", "USB device count=" + deviceList.size());
for (UsbDevice device : deviceList.values()) {
UsbInterface intf = findMidiInterface(device);
UsbInterface intf = UsbMidiDevice.findMidiInterface(device);
if (intf != null) {
if (usbManager.hasPermission(device)) {
if (setMidiInterface(device, intf)) {
usbDevice_ = device;
if (connectUsbMidi(device)) {
break;
}
} else {
@ -232,6 +171,14 @@ public class PianoActivity2 extends Activity {
}
}
boolean connectUsbMidi(UsbDevice device) {
if (synthesizerService_ != null) {
return synthesizerService_.connectUsbMidi(device);
}
usbDevicePending_ = device;
return true;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
boolean connectUsbFromIntent(Intent intent) {
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
@ -247,9 +194,6 @@ public class PianoActivity2 extends Activity {
permissionIntent_ = PendingIntent.getBroadcast(this, 0, new Intent(
ACTION_USB_PERMISSION), 0);
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);
filter.addAction(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver_, filter);
if (!connectUsbFromIntent(intent)) {
@ -262,16 +206,7 @@ public class PianoActivity2 extends Activity {
@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");
connectUsbFromIntent(intent);
} else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
Log.i("synth", "broadcast receiver: detach");
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (usbDevice_ != null && usbDevice_.equals(device)) {
connectUsbMidi(null);
}
} else if (ACTION_USB_PERMISSION.equals(action)) {
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
@ -300,6 +235,10 @@ public class PianoActivity2 extends Activity {
PianoActivity2.this, android.R.layout.simple_spinner_item, patchNames);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
presetSpinner_.setAdapter(adapter);
if (usbDevicePending_ != null) {
synthesizerService_.connectUsbMidi(usbDevicePending_);
usbDevicePending_ = null;
}
}
public void onServiceDisconnected(ComponentName className) {
synthesizerService_ = null;
@ -313,10 +252,7 @@ public class PianoActivity2 extends Activity {
private KnobView resonanceKnob_;
private KnobView overdriveKnob_;
private Spinner presetSpinner_;
private UsbDevice usbDevice_;
private UsbDeviceConnection usbMidiConnection_;
private UsbMidiDevice usbMidiDevice_;
private UsbInterface usbMidiInterface_;
private PendingIntent permissionIntent_;
private boolean permissionRequestPending_;
private UsbDevice usbDevicePending_;
}

@ -20,24 +20,27 @@ package com.levien.synthesizer.android.usb;
import android.annotation.TargetApi;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
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.AndroidGlue;
import com.levien.synthesizer.android.ui.PianoActivity2;
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public class UsbMidiDevice {
private final PianoActivity2 mActivity;
// probably want to change this to MessageOutputProcessor
private final AndroidGlue mReceiver;
private final UsbDeviceConnection mDeviceConnection;
private final UsbEndpoint mEndpoint;
private final WaiterThread mWaiterThread = new WaiterThread();
public UsbMidiDevice(PianoActivity2 activity, UsbDeviceConnection connection, UsbInterface intf) {
mActivity = activity;
public UsbMidiDevice(AndroidGlue receiver, UsbDeviceConnection connection, UsbInterface intf) {
mReceiver = receiver;
mDeviceConnection = connection;
mEndpoint = getInputEndpoint(intf);
@ -67,6 +70,19 @@ public class UsbMidiDevice {
}
}
// A helper function for clients that might want to query whether a
// device supports MIDI
public static 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;
}
private class WaiterThread extends Thread {
public boolean mStop;
@ -98,7 +114,7 @@ public class UsbMidiDevice {
byte[] newBuf = new byte[payloadBytes];
System.arraycopy(buf, i + 1, newBuf, 0, payloadBytes);
Log.d("synth", "sending midi");
mActivity.sendMidiBytes(newBuf);
mReceiver.sendMidi(newBuf);
}
}
}

Loading…
Cancel
Save