diff --git a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java b/android/src/com/levien/synthesizer/android/service/SynthesizerService.java index 2592fa2..9e508b5 100755 --- a/android/src/com/levien/synthesizer/android/service/SynthesizerService.java +++ b/android/src/com/levien/synthesizer/android/service/SynthesizerService.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 patchNames_; + + // State for USB MIDI keyboard connection + private static UsbDevice usbDevice_; + private UsbDeviceConnection usbMidiConnection_; + private UsbMidiDevice usbMidiDevice_; + private UsbInterface usbMidiInterface_; } diff --git a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java index 47ab87b..d98098a 100644 --- a/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java +++ b/android/src/com/levien/synthesizer/android/ui/PianoActivity2.java @@ -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 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_; } diff --git a/android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java b/android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java index 5cb80fc..25e6d34 100644 --- a/android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java +++ b/android/src/com/levien/synthesizer/android/usb/UsbMidiDevice.java @@ -1,12 +1,12 @@ /* * Copyright 2013 Google Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -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); } } }