From 16e99a3860c6635816717ea06ab3367e26f507ff Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Tue, 24 Dec 2013 14:28:18 -0800 Subject: [PATCH] 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). --- android/.classpath | 1 + .../android/service/SynthesizerService.java | 60 +++++++++++++++---- .../android/ui/PianoActivity2.java | 46 +++++--------- .../android/usb/UsbMidiDevice.java | 10 +++- 4 files changed, 71 insertions(+), 46 deletions(-) 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); } }