Skip to content

Commit

Permalink
[MIDI][Android] Support device attach event.
Browse files Browse the repository at this point in the history
This CL introduces device attach support for Android Web MIDI.

BUG=460901

Review URL: https://codereview.chromium.org/943213003

Cr-Commit-Position: refs/heads/master@{#318364}
  • Loading branch information
yutakahirano authored and Commit bot committed Feb 27, 2015
1 parent 780cc46 commit 2fff100
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ class UsbMidiDeviceAndroid {
*/
private long mNativePointer;

/**
* The underlying USB device.
*/
private UsbDevice mUsbDevice;

/**
* Audio interface subclass code for MIDI.
*/
Expand All @@ -79,6 +84,7 @@ class UsbMidiDeviceAndroid {
mEndpointMap = new SparseArray<UsbEndpoint>();
mRequestMap = new HashMap<UsbEndpoint, UsbRequest>();
mHandler = new Handler();
mUsbDevice = device;
mIsClosed = false;
mHasInputThread = false;
mNativePointer = 0;
Expand Down Expand Up @@ -177,6 +183,10 @@ public void run() {
});
}

UsbDevice getUsbDevice() {
return mUsbDevice;
}

/**
* Register the own native pointer.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Parcelable;

import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
Expand All @@ -35,7 +36,7 @@ class UsbMidiDeviceFactoryAndroid {
private UsbManager mUsbManager;

/**
* A BroadcastReceiver for USB device permission requests.
* A BroadcastReceiver for USB device events.
*/
private BroadcastReceiver mReceiver;

Expand All @@ -49,6 +50,11 @@ class UsbMidiDeviceFactoryAndroid {
*/
private Set<UsbDevice> mRequestedDevices;

/**
* True when the enumeration is in progress.
*/
private boolean mIsEnumeratingDevices;

/**
* The identifier of this factory.
*/
Expand All @@ -59,107 +65,141 @@ class UsbMidiDeviceFactoryAndroid {

/**
* Constructs a UsbMidiDeviceAndroid.
* @param context
* @param nativePointer The native pointer to which the created factory is associated.
*/
UsbMidiDeviceFactoryAndroid(long nativePointer) {
UsbMidiDeviceFactoryAndroid(Context context, long nativePointer) {
mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
mNativePointer = nativePointer;
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Parcelable extra = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
requestDevicePermissionIfNecessary(context, (UsbDevice) extra);
}
if (ACTION_USB_PERMISSION.equals(intent.getAction())) {
onUsbDevicePermissionRequestDone(context, intent);
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(ACTION_USB_PERMISSION);
context.registerReceiver(mReceiver, filter);
mRequestedDevices = new HashSet<UsbDevice>();
}

/**
* Constructs a UsbMidiDeviceAndroid.
* @param context
* @param nativePointer The native pointer to which the created factory is associated.
*/
@CalledByNative
static UsbMidiDeviceFactoryAndroid create(long nativePointer) {
return new UsbMidiDeviceFactoryAndroid(nativePointer);
static UsbMidiDeviceFactoryAndroid create(Context context, long nativePointer) {
return new UsbMidiDeviceFactoryAndroid(context, nativePointer);
}

/**
* Enumerates USB-MIDI devices.
* If there are devices having USB-MIDI interfaces, this function requests permission for
* accessing the device to the user.
* When the permission request is accepted or rejected onRequestDone will be called.
* When the permission request is accepted or rejected, nativeOnUsbMidiDeviceRequestDone
* will be called.
*
* If there are no USB-MIDI interfaces, this function returns false.
* @param context
* @return true if some permission requests are in progress.
*/
@CalledByNative
boolean enumerateDevices(Context context) {
mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
assert !mIsEnumeratingDevices;
mIsEnumeratingDevices = true;
Map<String, UsbDevice> devices = mUsbManager.getDeviceList();
PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, 0, new Intent(ACTION_USB_PERMISSION), 0);
mRequestedDevices = new HashSet<UsbDevice>();
for (UsbDevice device : devices.values()) {
boolean found = false;
for (int i = 0; i < device.getInterfaceCount() && !found; ++i) {
UsbInterface iface = device.getInterface(i);
if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO
&& iface.getInterfaceSubclass() == UsbMidiDeviceAndroid.MIDI_SUBCLASS) {
found = true;
}
}
if (found) {
mUsbManager.requestPermission(device, pendingIntent);
mRequestedDevices.add(device);
}
}
if (mRequestedDevices.isEmpty()) {
if (devices.isEmpty()) {
// No USB-MIDI devices are found.
mIsEnumeratingDevices = false;
return false;
}
for (UsbDevice device: devices.values()) {
requestDevicePermissionIfNecessary(context, device);
}
return true;
}

IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_USB_PERMISSION.equals(intent.getAction())) {
onRequestDone(context, intent);
}
/**
* Request a device access permission if there is a MIDI interface in the device.
*
* @param context
* @param device a USB device
*/
private void requestDevicePermissionIfNecessary(Context context, UsbDevice device) {
for (int i = 0; i < device.getInterfaceCount(); ++i) {
UsbInterface iface = device.getInterface(i);
if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO
&& iface.getInterfaceSubclass() == UsbMidiDeviceAndroid.MIDI_SUBCLASS) {
// There is at least one interface supporting MIDI.
mUsbManager.requestPermission(device, PendingIntent.getBroadcast(
context, 0, new Intent(ACTION_USB_PERMISSION), 0));
mRequestedDevices.add(device);
break;
}
};
context.registerReceiver(mReceiver, filter);
return true;
}
}

/**
* Called when the user accepts or rejects the permission request requested by
* EnumerateDevices.
* If all permission requests are responded, this function calls
* nativeOnUsbMidiDeviceRequestDone with the accessible USB-MIDI devices.
*
* @param context
* @param intent
*/
private void onRequestDone(Context context, Intent intent) {
private void onUsbDevicePermissionRequestDone(Context context, Intent intent) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (!mRequestedDevices.contains(device)) {
// We are not interested in the device.
return;
}
mRequestedDevices.remove(device);
if (!intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
// The request was rejected.
UsbMidiDeviceAndroid midiDevice = null;
if (mRequestedDevices.contains(device)) {
mRequestedDevices.remove(device);
if (!intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
// The request was rejected.
device = null;
}
} else {
device = null;
}

if (device != null) {
// Now we can add the device.
mDevices.add(new UsbMidiDeviceAndroid(mUsbManager, device));
midiDevice = new UsbMidiDeviceAndroid(mUsbManager, device);
mDevices.add(midiDevice);
}
if (mRequestedDevices.isEmpty()) {
// All requests are done.
context.unregisterReceiver(mReceiver);
if (mNativePointer != 0) {
nativeOnUsbMidiDeviceRequestDone(mNativePointer, mDevices.toArray());
}

if (!mRequestedDevices.isEmpty()) {
return;
}
if (mNativePointer == 0) {
return;
}

if (mIsEnumeratingDevices) {
nativeOnUsbMidiDeviceRequestDone(mNativePointer, mDevices.toArray());
mIsEnumeratingDevices = false;
} else if (midiDevice != null) {
nativeOnUsbMidiDeviceAttached(mNativePointer, midiDevice);
}
}

/**
* Disconnects the native object.
* @param context
*/
@CalledByNative
void close() {
void close(Context context) {
mNativePointer = 0;
context.unregisterReceiver(mReceiver);
}

private static native void nativeOnUsbMidiDeviceRequestDone(
long nativeUsbMidiDeviceFactoryAndroid, Object[] devices);
private static native void nativeOnUsbMidiDeviceAttached(
long nativeUsbMidiDeviceFactoryAndroid, Object device);
}
86 changes: 49 additions & 37 deletions media/midi/midi_manager_usb.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ void MidiManagerUsb::ReceiveUsbMidiData(UsbMidiDevice* device,
time);
}

void MidiManagerUsb::OnDeviceAttached(scoped_ptr<UsbMidiDevice> device) {
int device_id = devices_.size();
devices_.push_back(device.release());
AddPorts(devices_.back(), device_id);
}

void MidiManagerUsb::OnReceivedData(size_t jack_index,
const uint8* data,
size_t size,
Expand All @@ -82,50 +88,56 @@ void MidiManagerUsb::OnEnumerateDevicesDone(bool result,
initialize_callback_.Run(MIDI_INITIALIZATION_ERROR);
return;
}
input_stream_.reset(new UsbMidiInputStream(this));
devices->swap(devices_);
std::vector<UsbMidiJack> input_jacks;
for (size_t i = 0; i < devices_.size(); ++i) {
UsbMidiDescriptorParser parser;
std::vector<uint8> descriptor = devices_[i]->GetDescriptor();
const uint8* data = descriptor.size() > 0 ? &descriptor[0] : NULL;
std::vector<UsbMidiJack> jacks;
bool parse_result = parser.Parse(devices_[i],
data,
descriptor.size(),
&jacks);
if (!parse_result) {
if (!AddPorts(devices_[i], i)) {
initialize_callback_.Run(MIDI_INITIALIZATION_ERROR);
return;
}
for (size_t j = 0; j < jacks.size(); ++j) {
if (jacks[j].direction() == UsbMidiJack::DIRECTION_OUT) {
output_streams_.push_back(new UsbMidiOutputStream(jacks[j]));
// TODO(yhirano): Set appropriate properties.
// TODO(yhiran): Port ID should contain product ID / vendor ID.
// Port ID must be unique in a MIDI manager. This (and the below) ID
// setting is sufficiently unique although there is no user-friendly
// meaning.
MidiPortInfo port;
port.state = MIDI_PORT_OPENED;
port.id = base::StringPrintf("port-%ld-%ld",
static_cast<long>(i),
static_cast<long>(j));
AddOutputPort(port);
} else {
DCHECK_EQ(jacks[j].direction(), UsbMidiJack::DIRECTION_IN);
input_jacks.push_back(jacks[j]);
// TODO(yhirano): Set appropriate properties.
MidiPortInfo port;
port.state = MIDI_PORT_OPENED;
port.id = base::StringPrintf("port-%ld-%ld",
static_cast<long>(i),
static_cast<long>(j));
AddInputPort(port);
}
}
}
input_stream_.reset(new UsbMidiInputStream(input_jacks, this));
initialize_callback_.Run(MIDI_OK);
}

bool MidiManagerUsb::AddPorts(UsbMidiDevice* device, int device_id) {
UsbMidiDescriptorParser parser;
std::vector<uint8> descriptor = device->GetDescriptor();
const uint8* data = descriptor.size() > 0 ? &descriptor[0] : NULL;
std::vector<UsbMidiJack> jacks;
bool parse_result = parser.Parse(device,
data,
descriptor.size(),
&jacks);
if (!parse_result)
return false;

for (size_t j = 0; j < jacks.size(); ++j) {
if (jacks[j].direction() == UsbMidiJack::DIRECTION_OUT) {
output_streams_.push_back(new UsbMidiOutputStream(jacks[j]));
// TODO(yhirano): Set appropriate properties.
// TODO(yhiran): Port ID should contain product ID / vendor ID.
// Port ID must be unique in a MIDI manager. This (and the below) ID
// setting is sufficiently unique although there is no user-friendly
// meaning.
MidiPortInfo port;
port.state = MIDI_PORT_OPENED;
port.id = base::StringPrintf("port-%d-%ld",
device_id,
static_cast<long>(j));
AddOutputPort(port);
} else {
DCHECK_EQ(jacks[j].direction(), UsbMidiJack::DIRECTION_IN);
input_stream_->Add(jacks[j]);
// TODO(yhirano): Set appropriate properties.
MidiPortInfo port;
port.state = MIDI_PORT_OPENED;
port.id = base::StringPrintf("port-%d-%ld",
device_id,
static_cast<long>(j));
AddInputPort(port);
}
}
return true;
}

} // namespace media
2 changes: 2 additions & 0 deletions media/midi/midi_manager_usb.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class MEDIA_EXPORT MidiManagerUsb : public MidiManager,
const uint8* data,
size_t size,
base::TimeTicks time) override;
void OnDeviceAttached(scoped_ptr<UsbMidiDevice> device) override;

// UsbMidiInputStream::Delegate implementation.
void OnReceivedData(size_t jack_index,
Expand All @@ -67,6 +68,7 @@ class MEDIA_EXPORT MidiManagerUsb : public MidiManager,

private:
void OnEnumerateDevicesDone(bool result, UsbMidiDevice::Devices* devices);
bool AddPorts(UsbMidiDevice* device, int device_id);

scoped_ptr<UsbMidiDevice::Factory> device_factory_;
ScopedVector<UsbMidiDevice> devices_;
Expand Down
Loading

0 comments on commit 2fff100

Please sign in to comment.