diff --git a/android/.classpath b/android/.classpath index a607c7f..bea3b48 100644 --- a/android/.classpath +++ b/android/.classpath @@ -8,5 +8,6 @@ + diff --git a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java index 9e508b5..b9c85dc 100755 --- a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java +++ b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java @@ -19,6 +19,7 @@ package com.levien.synthesizer.android.service; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import android.annotation.TargetApi; @@ -69,6 +70,7 @@ public class SynthesizerService extends Service { * Run when the Service is first created. */ @Override + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) public void onCreate() { Log.d("synth", "service onCreate"); if (androidGlue_ == null) { @@ -98,12 +100,10 @@ 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); + scanUsbMidi(); } } @@ -112,6 +112,7 @@ public class SynthesizerService extends Service { */ @Override public void onDestroy() { + Log.d("synth", "service onDestroy"); androidGlue_.setPlayState(false); setMidiInterface(null, null); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { @@ -144,6 +145,27 @@ public class SynthesizerService extends Service { 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 { AudioParams(int sr, int bs) { confident = false; @@ -159,7 +181,7 @@ public class SynthesizerService extends Service { } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - void getJbMr1Params(AudioParams params) { + private void getJbMr1Params(AudioParams params) { AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); String sr = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 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 //usbMidiConnection_.releaseInterface(usbMidiInterface_); } + Log.d("synth", "closing connection " + usbMidiConnection_); usbMidiConnection_.close(); usbMidiConnection_ = null; } @@ -205,14 +228,24 @@ public class SynthesizerService extends Service { 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; + // 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 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 { + usbDeviceNeedsPermission_ = device; + } + } + } } private final BroadcastReceiver usbReceiver_ = new BroadcastReceiver() { @@ -237,8 +270,9 @@ public class SynthesizerService extends Service { private static List patchNames_; // State for USB MIDI keyboard connection - private static UsbDevice usbDevice_; + private UsbDevice usbDevice_; private UsbDeviceConnection usbMidiConnection_; private UsbMidiDevice usbMidiDevice_; private UsbInterface usbMidiInterface_; + private UsbDevice usbDeviceNeedsPermission_; } diff --git a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java index d98098a..5434aaa 100644 --- a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java +++ b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java @@ -145,32 +145,6 @@ public class PianoActivity2 extends Activity { 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 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) { if (synthesizerService_ != null) { return synthesizerService_.connectUsbMidi(device); @@ -196,9 +170,7 @@ public class PianoActivity2 extends Activity { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_USB_PERMISSION); registerReceiver(usbReceiver_, filter); - if (!connectUsbFromIntent(intent)) { - scanUsbMidi(); - } + connectUsbFromIntent(intent); } 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) { // TODO: in future we'll want to reflect MIDI to UI (knobs turn, keys press) - synthesizerService_.sendRawMidi(buf); + if (synthesizerService_ != null) { + synthesizerService_.sendRawMidi(buf); + } } private ServiceConnection synthesizerConnection_ = new ServiceConnection() { + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) public void onServiceConnected(ComponentName className, IBinder service) { SynthesizerService.LocalBinder binder = (SynthesizerService.LocalBinder)service; synthesizerService_ = binder.getService(); @@ -238,6 +213,17 @@ public class PianoActivity2 extends Activity { if (usbDevicePending_ != null) { synthesizerService_.connectUsbMidi(usbDevicePending_); 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) { diff --git a/android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java b/android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java index 25e6d34..a52e35c 100644 --- a/android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java +++ b/android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java @@ -95,10 +95,14 @@ public class UsbMidiDevice { 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); if (nBytes < 0) { - Log.e("synth", "bulkTransfer error " + nBytes); + //Log.e("synth", "bulkTransfer error " + nBytes); // break; } for (int i = 0; i < nBytes; i += 4) { @@ -113,7 +117,7 @@ public class UsbMidiDevice { if (payloadBytes > 0) { byte[] newBuf = new byte[payloadBytes]; System.arraycopy(buf, i + 1, newBuf, 0, payloadBytes); - Log.d("synth", "sending midi"); + //Log.d("synth", "sending midi"); mReceiver.sendMidi(newBuf); } }