Move USB bus scan to SynthesizerService

This patch moves the scan of the USB bus to the service, while plumbing
the display of the permission dialog to the activity that binds it. Also
a little cleanup of the USB device thread (less logging, using a hacky
timeout to make sure the thread gets shut down properly).
master
Raph Levien 11 years ago
parent a762514187
commit 16e99a3860
  1. 1
      android/.classpath
  2. 60
      android/src/com/levien/synthesizer/android/service/SynthesizerService.java
  3. 44
      android/src/com/levien/synthesizer/android/ui/PianoActivity2.java
  4. 10
      android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java

@ -8,5 +8,6 @@
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="lib" path="core-lib/libprotobuf.jar"/> <classpathentry exported="true" kind="lib" path="core-lib/libprotobuf.jar"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="output" path="bin/classes"/> <classpathentry kind="output" path="bin/classes"/>
</classpath> </classpath>

@ -19,6 +19,7 @@ package com.levien.synthesizer.android.service;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import android.annotation.TargetApi; import android.annotation.TargetApi;
@ -69,6 +70,7 @@ public class SynthesizerService extends Service {
* Run when the Service is first created. * Run when the Service is first created.
*/ */
@Override @Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public void onCreate() { public void onCreate() {
Log.d("synth", "service onCreate"); Log.d("synth", "service onCreate");
if (androidGlue_ == null) { if (androidGlue_ == null) {
@ -98,12 +100,10 @@ public class SynthesizerService extends Service {
} }
} }
androidGlue_.setPlayState(true); androidGlue_.setPlayState(true);
if (usbDevice_ != null && usbMidiConnection_ == null) {
connectUsbMidi(usbDevice_);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED); IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(usbReceiver_, filter); registerReceiver(usbReceiver_, filter);
scanUsbMidi();
} }
} }
@ -112,6 +112,7 @@ public class SynthesizerService extends Service {
*/ */
@Override @Override
public void onDestroy() { public void onDestroy() {
Log.d("synth", "service onDestroy");
androidGlue_.setPlayState(false); androidGlue_.setPlayState(false);
setMidiInterface(null, null); setMidiInterface(null, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
@ -144,6 +145,27 @@ public class SynthesizerService extends Service {
return patchNames_; return patchNames_;
} }
public boolean connectUsbMidi(UsbDevice device) {
usbDeviceNeedsPermission_ = null;
if (usbDevice_ == device) {
return device != null;
}
UsbInterface intf = device != null ? UsbMidiDevice.findMidiInterface(device) : null;
boolean success = setMidiInterface(device, intf);
usbDevice_ = success ? device : null;
return success;
}
/**
* Call to find out whether there is a device that has been scanned
* but not connected to because of missing permission.
*
* @return Device that needs permission, or null if none.
*/
public UsbDevice usbDeviceNeedsPermission() {
return usbDeviceNeedsPermission_;
}
class AudioParams { class AudioParams {
AudioParams(int sr, int bs) { AudioParams(int sr, int bs) {
confident = false; confident = false;
@ -159,7 +181,7 @@ public class SynthesizerService extends Service {
} }
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
void getJbMr1Params(AudioParams params) { private void getJbMr1Params(AudioParams params) {
AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
String sr = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); String sr = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
String bs = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); String bs = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
@ -180,6 +202,7 @@ public class SynthesizerService extends Service {
// based on experimentation just closing seems more robust // based on experimentation just closing seems more robust
//usbMidiConnection_.releaseInterface(usbMidiInterface_); //usbMidiConnection_.releaseInterface(usbMidiInterface_);
} }
Log.d("synth", "closing connection " + usbMidiConnection_);
usbMidiConnection_.close(); usbMidiConnection_.close();
usbMidiConnection_ = null; usbMidiConnection_ = null;
} }
@ -205,14 +228,24 @@ public class SynthesizerService extends Service {
return false; return false;
} }
// Handles both connect and disconnect actions // scan for MIDI devices on the USB bus
public boolean connectUsbMidi(UsbDevice device) { @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
UsbInterface intf = device != null ? UsbMidiDevice.findMidiInterface(device) : null; private void scanUsbMidi() {
Log.d("synth", "connecting USB"); UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
boolean success = setMidiInterface(device, intf); HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
Log.d("synth", "connecting USB done"); Log.i("synth", "USB device count=" + deviceList.size());
usbDevice_ = success ? device : null; for (UsbDevice device : deviceList.values()) {
return success; UsbInterface intf = UsbMidiDevice.findMidiInterface(device);
if (intf != null) {
if (usbManager.hasPermission(device)) {
if (connectUsbMidi(device)) {
break;
}
} else {
usbDeviceNeedsPermission_ = device;
}
}
}
} }
private final BroadcastReceiver usbReceiver_ = new BroadcastReceiver() { private final BroadcastReceiver usbReceiver_ = new BroadcastReceiver() {
@ -237,8 +270,9 @@ public class SynthesizerService extends Service {
private static List<String> patchNames_; private static List<String> patchNames_;
// State for USB MIDI keyboard connection // State for USB MIDI keyboard connection
private static UsbDevice usbDevice_; private UsbDevice usbDevice_;
private UsbDeviceConnection usbMidiConnection_; private UsbDeviceConnection usbMidiConnection_;
private UsbMidiDevice usbMidiDevice_; private UsbMidiDevice usbMidiDevice_;
private UsbInterface usbMidiInterface_; private UsbInterface usbMidiInterface_;
private UsbDevice usbDeviceNeedsPermission_;
} }

@ -145,32 +145,6 @@ public class PianoActivity2 extends Activity {
connectUsbFromIntent(intent); connectUsbFromIntent(intent);
} }
// scan for MIDI devices on the USB bus
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private void scanUsbMidi() {
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
Log.i("synth", "USB device count=" + deviceList.size());
for (UsbDevice device : deviceList.values()) {
UsbInterface intf = UsbMidiDevice.findMidiInterface(device);
if (intf != null) {
if (usbManager.hasPermission(device)) {
if (connectUsbMidi(device)) {
break;
}
} else {
synchronized (usbReceiver_) {
if (!permissionRequestPending_) {
permissionRequestPending_ = true;
usbManager.requestPermission(device, permissionIntent_);
}
}
break; // Don't try to connect anything else after perms dialog up
}
}
}
}
boolean connectUsbMidi(UsbDevice device) { boolean connectUsbMidi(UsbDevice device) {
if (synthesizerService_ != null) { if (synthesizerService_ != null) {
return synthesizerService_.connectUsbMidi(device); return synthesizerService_.connectUsbMidi(device);
@ -196,9 +170,7 @@ public class PianoActivity2 extends Activity {
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_USB_PERMISSION); filter.addAction(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver_, filter); registerReceiver(usbReceiver_, filter);
if (!connectUsbFromIntent(intent)) { connectUsbFromIntent(intent);
scanUsbMidi();
}
} }
private static final String ACTION_USB_PERMISSION = "com.levien.synthesizer.USB_PERSMISSION"; private static final String ACTION_USB_PERMISSION = "com.levien.synthesizer.USB_PERSMISSION";
@ -222,10 +194,13 @@ public class PianoActivity2 extends Activity {
public void sendMidiBytes(byte[] buf) { public void sendMidiBytes(byte[] buf) {
// TODO: in future we'll want to reflect MIDI to UI (knobs turn, keys press) // TODO: in future we'll want to reflect MIDI to UI (knobs turn, keys press)
if (synthesizerService_ != null) {
synthesizerService_.sendRawMidi(buf); synthesizerService_.sendRawMidi(buf);
} }
}
private ServiceConnection synthesizerConnection_ = new ServiceConnection() { private ServiceConnection synthesizerConnection_ = new ServiceConnection() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
public void onServiceConnected(ComponentName className, IBinder service) { public void onServiceConnected(ComponentName className, IBinder service) {
SynthesizerService.LocalBinder binder = (SynthesizerService.LocalBinder)service; SynthesizerService.LocalBinder binder = (SynthesizerService.LocalBinder)service;
synthesizerService_ = binder.getService(); synthesizerService_ = binder.getService();
@ -238,6 +213,17 @@ public class PianoActivity2 extends Activity {
if (usbDevicePending_ != null) { if (usbDevicePending_ != null) {
synthesizerService_.connectUsbMidi(usbDevicePending_); synthesizerService_.connectUsbMidi(usbDevicePending_);
usbDevicePending_ = null; usbDevicePending_ = null;
} else {
UsbDevice device = synthesizerService_.usbDeviceNeedsPermission();
if (device != null) {
synchronized (usbReceiver_) {
if (!permissionRequestPending_) {
permissionRequestPending_ = true;
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
usbManager.requestPermission(device, permissionIntent_);
}
}
}
} }
} }
public void onServiceDisconnected(ComponentName className) { public void onServiceDisconnected(ComponentName className) {

@ -95,10 +95,14 @@ public class UsbMidiDevice {
return; return;
} }
} }
final int TIMEOUT = 0; // Using a timeout here is a hacky workaround to shut down the
// thread. If we could call releaseInterface, that would cause
// the bulkTransfer to return immediately, but that causes other
// problems.
final int TIMEOUT = 1000;
int nBytes = mDeviceConnection.bulkTransfer(mEndpoint, buf, buf.length, TIMEOUT); int nBytes = mDeviceConnection.bulkTransfer(mEndpoint, buf, buf.length, TIMEOUT);
if (nBytes < 0) { if (nBytes < 0) {
Log.e("synth", "bulkTransfer error " + nBytes); //Log.e("synth", "bulkTransfer error " + nBytes);
// break; // break;
} }
for (int i = 0; i < nBytes; i += 4) { for (int i = 0; i < nBytes; i += 4) {
@ -113,7 +117,7 @@ public class UsbMidiDevice {
if (payloadBytes > 0) { if (payloadBytes > 0) {
byte[] newBuf = new byte[payloadBytes]; byte[] newBuf = new byte[payloadBytes];
System.arraycopy(buf, i + 1, newBuf, 0, payloadBytes); System.arraycopy(buf, i + 1, newBuf, 0, payloadBytes);
Log.d("synth", "sending midi"); //Log.d("synth", "sending midi");
mReceiver.sendMidi(newBuf); mReceiver.sendMidi(newBuf);
} }
} }

Loading…
Cancel
Save