MidiManager updates:
MIDI ports are now implemented as file descriptors directly between the sender
and receiver, so the MidiService is no longer in the message path.
To facilitate the above, each port has its own file descriptor, rather than multiplexing
all ports on a device through a single socket.
Added a new class MidiDeviceServer, which is used by implementors of MIDI devices.
This replaces the MidiVirtualDevice class (which only was included in changes that were reviewed but never submitted).
The USB MIDI implementation has moved from the MIDI service to the USB service.
The USB MIDI implementation uses MidiDeviceServer as its interface, so we now have a common
interface for all MIDI device implementations.
Change-Id: I8effd1583f344beb6c940c3a24dbf20b477a6436
diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java
new file mode 100644
index 0000000..38f1cb8
--- /dev/null
+++ b/services/core/java/com/android/server/MidiService.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.
+ * See the License for the specific language governing permissions an
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.midi.IMidiDeviceServer;
+import android.midi.IMidiListener;
+import android.midi.IMidiManager;
+import android.midi.MidiDeviceInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class MidiService extends IMidiManager.Stub {
+ private static final String TAG = "MidiService";
+
+ private final Context mContext;
+
+ // list of all our clients, keyed by Binder token
+ private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
+
+ // list of all devices, keyed by MidiDeviceInfo
+ private final HashMap<MidiDeviceInfo, Device> mDevicesByInfo
+ = new HashMap<MidiDeviceInfo, Device>();
+
+ // list of all devices, keyed by IMidiDeviceServer
+ private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>();
+
+ // used for assigning IDs to MIDI devices
+ private int mNextDeviceId = 1;
+
+ private final class Client implements IBinder.DeathRecipient {
+ // Binder token for this client
+ private final IBinder mToken;
+ // This client's UID
+ private final int mUid;
+ // This client's PID
+ private final int mPid;
+ // List of all receivers for this client
+ private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
+
+ public Client(IBinder token) {
+ mToken = token;
+ mUid = Binder.getCallingUid();
+ mPid = Binder.getCallingPid();
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public void addListener(IMidiListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void removeListener(IMidiListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ removeClient(mToken);
+ }
+ }
+
+ public void deviceAdded(Device device) {
+ // ignore private devices that our client cannot access
+ if (!device.isUidAllowed(mUid)) return;
+
+ MidiDeviceInfo deviceInfo = device.getDeviceInfo();
+ try {
+ for (IMidiListener listener : mListeners) {
+ listener.onDeviceAdded(deviceInfo);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception", e);
+ }
+ }
+
+ public void deviceRemoved(Device device) {
+ // ignore private devices that our client cannot access
+ if (!device.isUidAllowed(mUid)) return;
+
+ MidiDeviceInfo deviceInfo = device.getDeviceInfo();
+ try {
+ for (IMidiListener listener : mListeners) {
+ listener.onDeviceRemoved(deviceInfo);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception", e);
+ }
+ }
+
+ public void binderDied() {
+ removeClient(mToken);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Client: UID: ");
+ sb.append(mUid);
+ sb.append(" PID: ");
+ sb.append(mPid);
+ sb.append(" listener count: ");
+ sb.append(mListeners.size());
+ return sb.toString();
+ }
+ }
+
+ private Client getClient(IBinder token) {
+ synchronized (mClients) {
+ Client client = mClients.get(token);
+ if (client == null) {
+ client = new Client(token);
+
+ try {
+ token.linkToDeath(client, 0);
+ } catch (RemoteException e) {
+ return null;
+ }
+ mClients.put(token, client);
+ }
+ return client;
+ }
+ }
+
+ private void removeClient(IBinder token) {
+ mClients.remove(token);
+ }
+
+ private final class Device implements IBinder.DeathRecipient {
+ private final IMidiDeviceServer mServer;
+ private final MidiDeviceInfo mDeviceInfo;
+ // UID of device creator
+ private final int mUid;
+ // PID of device creator
+ private final int mPid;
+
+ public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo) {
+ mServer = server;
+ mDeviceInfo = deviceInfo;
+ mUid = Binder.getCallingUid();
+ mPid = Binder.getCallingPid();
+ }
+
+ public MidiDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
+
+ public IMidiDeviceServer getDeviceServer() {
+ return mServer;
+ }
+
+ public boolean isUidAllowed(int uid) {
+ // FIXME
+ return true;
+ }
+
+ public void binderDied() {
+ synchronized (mDevicesByServer) {
+ removeDeviceLocked(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Device: ");
+ sb.append(mDeviceInfo);
+ sb.append(" UID: ");
+ sb.append(mUid);
+ sb.append(" PID: ");
+ sb.append(mPid);
+ return sb.toString();
+ }
+ }
+
+ public MidiService(Context context) {
+ mContext = context;
+ }
+
+ public void registerListener(IBinder token, IMidiListener listener) {
+ Client client = getClient(token);
+ if (client == null) return;
+ client.addListener(listener);
+ }
+
+ public void unregisterListener(IBinder token, IMidiListener listener) {
+ Client client = getClient(token);
+ if (client == null) return;
+ client.removeListener(listener);
+ }
+
+ public MidiDeviceInfo[] getDeviceList() {
+ return mDevicesByInfo.keySet().toArray(new MidiDeviceInfo[0]);
+ }
+
+ public IMidiDeviceServer openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
+ Device device = mDevicesByInfo.get(deviceInfo);
+ if (device == null) {
+ Log.e(TAG, "device not found in openDevice: " + deviceInfo);
+ return null;
+ }
+
+ if (!device.isUidAllowed(Binder.getCallingUid())) {
+ throw new SecurityException("Attempt to open private device with wrong UID");
+ }
+
+ return device.getDeviceServer();
+ }
+
+ public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
+ int numOutputPorts, Bundle properties, boolean isPrivate, int type) {
+ if (type != MidiDeviceInfo.TYPE_VIRTUAL && Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("only system can create non-virtual devices");
+ }
+
+ MidiDeviceInfo deviceInfo;
+ Device device;
+
+ synchronized (mDevicesByServer) {
+ int id = mNextDeviceId++;
+ deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, properties);
+ IBinder binder = server.asBinder();
+ device = new Device(server, deviceInfo);
+ try {
+ binder.linkToDeath(device, 0);
+ } catch (RemoteException e) {
+ return null;
+ }
+ mDevicesByInfo.put(deviceInfo, device);
+ mDevicesByServer.put(server.asBinder(), device);
+ }
+
+ synchronized (mClients) {
+ for (Client c : mClients.values()) {
+ c.deviceAdded(device);
+ }
+ }
+
+ return deviceInfo;
+ }
+
+ public void unregisterDeviceServer(IMidiDeviceServer server) {
+ synchronized (mDevicesByServer) {
+ removeDeviceLocked(mDevicesByServer.get(server.asBinder()));
+ }
+ }
+
+ // synchronize on mDevicesByServer
+ private void removeDeviceLocked(Device device) {
+ if (mDevicesByServer.remove(device.getDeviceServer().asBinder()) != null) {
+ mDevicesByInfo.remove(device.getDeviceInfo());
+
+ synchronized (mClients) {
+ for (Client c : mClients.values()) {
+ c.deviceRemoved(device);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+
+ pw.println("MIDI Manager State:");
+ pw.increaseIndent();
+
+ pw.println("Devices:");
+ pw.increaseIndent();
+ for (Device device : mDevicesByInfo.values()) {
+ pw.println(device.toString());
+ }
+ pw.decreaseIndent();
+
+ pw.println("Clients:");
+ pw.increaseIndent();
+ for (Client client : mClients.values()) {
+ pw.println(client.toString());
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/midi/MidiDeviceBase.java b/services/core/java/com/android/server/midi/MidiDeviceBase.java
deleted file mode 100644
index 4289584..0000000
--- a/services/core/java/com/android/server/midi/MidiDeviceBase.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.
- * See the License for the specific language governing permissions an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.os.Binder;
-import android.os.ParcelFileDescriptor;
-import android.system.OsConstants;
-import android.util.Log;
-
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * Abstract internal base class for entities in MidiService.
- * This class contains two threads for reading and writing MIDI events.
- * On one end we have the readMessage() and writeMessage() methods, which must be
- * implemented by a subclass. On the other end we have file descriptors for sockets
- * attached to client applications.
- */
-abstract class MidiDeviceBase {
- private static final String TAG = "MidiDeviceBase";
-
- final MidiDeviceInfo mDeviceInfo;
- private ReaderThread mReaderThread;
- private WriterThread mWriterThread;
- private ParcelFileDescriptor mParcelFileDescriptor;
-
- // Reads MIDI messages from readMessage() and write them to one or more clients.
- private class ReaderThread extends Thread {
- private final ArrayList<FileOutputStream> mOutputStreams =
- new ArrayList<FileOutputStream>();
-
- @Override
- public void run() {
- byte[] buffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
-
- while (true) {
- try {
- int count = readMessage(buffer);
-
- if (count > 0) {
- synchronized (mOutputStreams) {
- for (int i = 0; i < mOutputStreams.size(); i++) {
- FileOutputStream fos = mOutputStreams.get(i);
- try {
- fos.write(buffer, 0, count);
- } catch (IOException e) {
- Log.e(TAG, "write failed", e);
- mOutputStreams.remove(fos);
- }
- }
- }
- }
- } catch (IOException e) {
- Log.e(TAG, "read failed", e);
- break;
- }
- }
- }
-
- public void addListener(FileOutputStream fos) {
- synchronized (mOutputStreams) {
- mOutputStreams.add(fos);
- }
- }
-
- public void removeListener(FileOutputStream fos) {
- synchronized (mOutputStreams) {
- mOutputStreams.remove(fos);
- }
- }
- }
-
- // Reads MIDI messages from our client and writes them by calling writeMessage()
- private class WriterThread extends Thread {
- private final FileInputStream mInputStream;
-
- public WriterThread(FileInputStream fis) {
- mInputStream = fis;
- }
-
- @Override
- public void run() {
- byte[] buffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
-
- while (true) {
- try {
- int count = mInputStream.read(buffer);
- writeMessage(buffer, count);
- } catch (IOException e) {
- Log.e(TAG, "WriterThread failed", e);
- break;
- }
- }
- }
- }
-
- public MidiDeviceBase(MidiDeviceInfo info) {
- mDeviceInfo = info;
- }
-
- public MidiDeviceInfo getInfo() {
- return mDeviceInfo;
- }
-
- public ParcelFileDescriptor getFileDescriptor() {
- synchronized (this) {
- if (mReaderThread == null) {
- if (!open()) {
- return null;
- }
- mReaderThread = new ReaderThread();
- mReaderThread.start();
- }
- }
-
- try {
- ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
- OsConstants.SOCK_SEQPACKET);
- mParcelFileDescriptor = pair[0];
- FileOutputStream fos = new FileOutputStream(mParcelFileDescriptor.getFileDescriptor());
- mReaderThread.addListener(fos);
-
- // return an error if the device is already open for writing?
- if (mWriterThread == null) {
- FileInputStream fis = new FileInputStream(
- mParcelFileDescriptor.getFileDescriptor());
- mWriterThread = new WriterThread(fis);
- mWriterThread.start();
- }
-
- return pair[1];
- } catch (IOException e) {
- Log.e(TAG, "could not create ParcelFileDescriptor pair", e);
- return null;
- }
- }
-
- abstract boolean open();
-
- void close() {
- try {
- if (mParcelFileDescriptor != null) {
- mParcelFileDescriptor.close();
- }
- } catch (IOException e) {
- }
- }
-
- abstract int readMessage(byte[] buffer) throws IOException;
- abstract void writeMessage(byte[] buffer, int count) throws IOException;
-}
diff --git a/services/core/java/com/android/server/midi/MidiService.java b/services/core/java/com/android/server/midi/MidiService.java
deleted file mode 100644
index 29a8339..0000000
--- a/services/core/java/com/android/server/midi/MidiService.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.
- * See the License for the specific language governing permissions an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.content.Context;
-import android.hardware.usb.UsbDevice;
-import android.midi.IMidiListener;
-import android.midi.IMidiManager;
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class MidiService extends IMidiManager.Stub {
- private static final String TAG = "MidiService";
-
- private final Context mContext;
-
- // list of all our clients, keyed by Binder token
- private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
-
- // list of all devices, keyed by ID
- private final HashMap<Integer, MidiDeviceBase> mDevices
- = new HashMap<Integer, MidiDeviceBase>();
-
- // list of all USB devices, keyed by USB device.
- private final HashMap<UsbDevice, UsbMidiDevice> mUsbDevices
- = new HashMap<UsbDevice, UsbMidiDevice>();
-
- // used for assigning IDs to MIDI devices
- private int mNextDeviceId = 1;
-
- private final class Client implements IBinder.DeathRecipient {
- private final IBinder mToken;
- private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
- private final ArrayList<MidiDeviceBase> mVirtualDevices = new ArrayList<MidiDeviceBase>();
-
- public Client(IBinder token) {
- mToken = token;
- }
-
- public void close() {
- for (MidiDeviceBase device : mVirtualDevices) {
- device.close();
- }
- mVirtualDevices.clear();
- }
-
- public void addListener(IMidiListener listener) {
- mListeners.add(listener);
- }
-
- public void removeListener(IMidiListener listener) {
- mListeners.remove(listener);
- if (mListeners.size() == 0 && mVirtualDevices.size() == 0) {
- removeClient(mToken);
- }
- }
-
- public void addVirtualDevice(MidiDeviceBase device) {
- mVirtualDevices.add(device);
- }
-
- public void removeVirtualDevice(MidiDeviceBase device) {
- mVirtualDevices.remove(device);
- }
-
- public void deviceAdded(MidiDeviceInfo device) {
- try {
- for (IMidiListener listener : mListeners) {
- listener.onDeviceAdded(device);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "remote exception", e);
- }
- }
-
- public void deviceRemoved(MidiDeviceInfo device) {
- try {
- for (IMidiListener listener : mListeners) {
- listener.onDeviceRemoved(device);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "remote exception", e);
- }
- }
-
- public void binderDied() {
- removeClient(mToken);
- }
- }
-
-
- private Client getClient(IBinder token) {
- synchronized (mClients) {
- Client client = mClients.get(token);
- if (client == null) {
- client = new Client(token);
-
- try {
- token.linkToDeath(client, 0);
- } catch (RemoteException e) {
- return null;
- }
- mClients.put(token, client);
- }
- return client;
- }
- }
-
- private void removeClient(IBinder token) {
- synchronized (mClients) {
- Client client = mClients.remove(token);
- if (client != null) {
- client.close();
- }
- }
- }
-
- public MidiService(Context context) {
- mContext = context;
- }
-
- public void registerListener(IBinder token, IMidiListener listener) {
- Client client = getClient(token);
- if (client == null) return;
- client.addListener(listener);
- }
-
- public void unregisterListener(IBinder token, IMidiListener listener) {
- Client client = getClient(token);
- if (client == null) return;
- client.removeListener(listener);
- }
-
- public MidiDeviceInfo[] getDeviceList() {
- ArrayList<MidiDeviceInfo> infos = new ArrayList<MidiDeviceInfo>();
- for (MidiDeviceBase device : mDevices.values()) {
- infos.add(device.getInfo());
- }
- return infos.toArray(new MidiDeviceInfo[0]);
- }
-
- public ParcelFileDescriptor openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
- MidiDeviceBase device = mDevices.get(deviceInfo.getId());
- if (device == null) {
- Log.e(TAG, "device not found in openDevice: " + deviceInfo);
- return null;
- }
-
- return device.getFileDescriptor();
- }
-
- public MidiDevice registerVirtualDevice(IBinder token, int numInputPorts, int numOutputPorts,
- Bundle properties) {
- VirtualMidiDevice device;
- Client client = getClient(token);
- if (client == null) return null;
-
- synchronized (mDevices) {
- int id = mNextDeviceId++;
- MidiDeviceInfo deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_VIRTUAL, id,
- numInputPorts, numOutputPorts, properties);
-
- device = new VirtualMidiDevice(deviceInfo);
- if (!device.open()) {
- return null;
- }
- mDevices.put(id, device);
- client.addVirtualDevice(device);
- }
-
- synchronized (mClients) {
- MidiDeviceInfo deviceInfo = device.getInfo();
- for (Client c : mClients.values()) {
- c.deviceAdded(deviceInfo);
- }
- }
-
- return device.getProxy();
- }
-
- public void unregisterVirtualDevice(IBinder token, MidiDeviceInfo deviceInfo) {
- Client client = getClient(token);
- if (client == null) return;
-
- MidiDeviceBase device;
- synchronized (mDevices) {
- device = mDevices.remove(deviceInfo.getId());
- }
-
- if (device != null) {
- client.removeVirtualDevice(device);
- device.close();
-
- synchronized (mClients) {
- for (Client c : mClients.values()) {
- c.deviceRemoved(deviceInfo);
- }
- }
- }
- }
-
- // called by UsbAudioManager to notify of new USB MIDI devices
- public void alsaDeviceAdded(int card, int device, UsbDevice usbDevice) {
- Log.d(TAG, "alsaDeviceAdded: card:" + card + " device:" + device);
-
- MidiDeviceInfo deviceInfo;
-
- synchronized (mDevices) {
- int id = mNextDeviceId++;
- Bundle properties = new Bundle();
- properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER,
- usbDevice.getManufacturerName());
- properties.putString(MidiDeviceInfo.PROPERTY_MODEL,
- usbDevice.getProductName());
- properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
- usbDevice.getSerialNumber());
- properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
-
- // FIXME - multiple ports not supported yet
- int inputPorts = 1;
- int outputPorts = 1;
-
- deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_USB, id, inputPorts, outputPorts,
- properties, card, device);
- UsbMidiDevice midiDevice = new UsbMidiDevice(deviceInfo);
- mDevices.put(id, midiDevice);
- mUsbDevices.put(usbDevice, midiDevice);
- }
-
- synchronized (mClients) {
- for (Client client : mClients.values()) {
- client.deviceAdded(deviceInfo);
- }
- }
- }
-
- // called by UsbAudioManager to notify of removed USB MIDI devices
- public void alsaDeviceRemoved(UsbDevice usbDevice) {
- MidiDeviceInfo deviceInfo = null;
-
- synchronized (mDevices) {
- MidiDeviceBase device = mUsbDevices.remove(usbDevice);
- if (device != null) {
- device.close();
- deviceInfo = device.getInfo();
- mDevices.remove(deviceInfo.getId());
- }
- }
-
- Log.d(TAG, "alsaDeviceRemoved: " + deviceInfo);
-
- if (deviceInfo != null) {
- synchronized (mClients) {
- for (Client client : mClients.values()) {
- client.deviceRemoved(deviceInfo);
- }
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/midi/UsbMidiDevice.java b/services/core/java/com/android/server/midi/UsbMidiDevice.java
deleted file mode 100644
index 3d42c67..0000000
--- a/services/core/java/com/android/server/midi/UsbMidiDevice.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.
- * See the License for the specific language governing permissions an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.midi.MidiUtils;
-import android.os.Binder;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-// This is our subclass of MidiDeviceBase for communicating with USB MIDI devices
-// via the ALSA driver file system.
-class UsbMidiDevice extends MidiDeviceBase {
- private static final String TAG = "UsbMidiDevice";
-
- private FileInputStream mInputStream;
- private FileOutputStream mOutputStream;
- private final byte[] mBuffer = new byte[3];
-
- public UsbMidiDevice(MidiDeviceInfo info) {
- super(info);
- }
-
- public boolean open() {
- if (mInputStream != null && mOutputStream != null) {
- // already open
- return true;
- }
-
- int card = mDeviceInfo.getAlsaCard();
- int device = mDeviceInfo.getAlsaDevice();
- if (card == -1 || device == -1) {
- Log.e(TAG, "Not a USB device!");
- return false;
- }
-
- // clear calling identity so we can access the driver file.
- long identity = Binder.clearCallingIdentity();
-
- File file = new File("/dev/snd/midiC" + card + "D" + device);
- try {
- mInputStream = new FileInputStream(file);
- mOutputStream = new FileOutputStream(file);
- } catch (Exception e) {
- Log.e(TAG, "could not open " + file);
- return false;
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
-
- return true;
- }
-
- void close() {
- super.close();
- try {
- if (mInputStream != null) {
- mInputStream.close();
- }
- if (mOutputStream != null) {
- mOutputStream.close();
- }
- } catch (IOException e) {
- }
- }
-
- // Reads a message from the ALSA driver.
- // The driver may return multiple messages, so we have to read byte at a time.
- int readMessage(byte[] buffer) throws IOException {
- if (mInputStream.read(mBuffer, 0, 1) != 1) {
- Log.e(TAG, "could not read command byte");
- return -1;
- }
- int dataSize = MidiUtils.getMessageDataSize(mBuffer[0]);
- if (dataSize < 0) {
- return -1;
- }
- if (dataSize > 0) {
- if (mInputStream.read(mBuffer, 1, dataSize) != dataSize) {
- Log.e(TAG, "could not read command data");
- return -1;
- }
- }
- return MidiDevice.packMessage(mBuffer, 0, dataSize + 1, System.nanoTime(),
- 0, // FIXME - multiple ports not supported yet
- buffer);
- }
-
- // writes a message to the ALSA driver
- void writeMessage(byte[] buffer, int count) throws IOException {
- int offset = MidiDevice.getMessageOffset(buffer, count);
- int size = MidiDevice.getMessageSize(buffer, count);
- // FIXME - multiple ports not supported yet
- mOutputStream.write(buffer, offset, count);
- }
-}
-
diff --git a/services/core/java/com/android/server/midi/VirtualMidiDevice.java b/services/core/java/com/android/server/midi/VirtualMidiDevice.java
deleted file mode 100644
index 5b39045..0000000
--- a/services/core/java/com/android/server/midi/VirtualMidiDevice.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.
- * See the License for the specific language governing permissions an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.os.ParcelFileDescriptor;
-import android.system.OsConstants;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-// Our subclass of MidiDeviceBase to implement a virtual MIDI device
-class VirtualMidiDevice extends MidiDeviceBase {
- private static final String TAG = "VirtualMidiDevice";
-
- private ParcelFileDescriptor[] mFileDescriptors;
- private FileInputStream mInputStream;
- private FileOutputStream mOutputStream;
-
- public VirtualMidiDevice(MidiDeviceInfo info) {
- super(info);
- }
-
- public boolean open() {
- if (mInputStream != null && mOutputStream != null) {
- // already open
- return true;
- }
-
- try {
- mFileDescriptors = ParcelFileDescriptor.createSocketPair(
- OsConstants.SOCK_SEQPACKET);
- FileDescriptor fd = mFileDescriptors[0].getFileDescriptor();
- mInputStream = new FileInputStream(fd);
- mOutputStream = new FileOutputStream(fd);
- return true;
- } catch (IOException e) {
- Log.e(TAG, "failed to create ParcelFileDescriptor pair");
- return false;
- }
- }
-
- void close() {
- super.close();
- try {
- if (mInputStream != null) {
- mInputStream.close();
- }
- if (mOutputStream != null) {
- mOutputStream.close();
- }
- if (mFileDescriptors != null && mFileDescriptors[0] != null) {
- mFileDescriptors[0].close();
- // file descriptor 1 is passed to client process
- }
- } catch (IOException e) {
- }
- }
-
- MidiDevice getProxy() {
- return new MidiDevice(mDeviceInfo, mFileDescriptors[1]);
- }
-
- int readMessage(byte[] buffer) throws IOException {
- int ret = mInputStream.read(buffer);
- // for now, throw away the timestamp
- return ret - 8;
- }
-
- void writeMessage(byte[] buffer, int count) throws IOException {
- mOutputStream.write(buffer, 0, count);
- }
-}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index dc073ad..7b74e91 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -22,6 +22,7 @@
$(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
$(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
$(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp
new file mode 100644
index 0000000..94853b8
--- /dev/null
+++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "UsbMidiDeviceJNI"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/Log.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <asm/byteorder.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sound/asound.h>
+
+namespace android
+{
+
+static jclass sFileDescriptorClass;
+
+static jint
+android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */,
+ jint card, jint device)
+{
+ char path[100];
+
+ snprintf(path, sizeof(path), "/dev/snd/controlC%d", card);
+ int fd = open(path, O_RDWR);
+ if (fd < 0) {
+ ALOGE("could not open %s", path);
+ return 0;
+ }
+
+ struct snd_rawmidi_info info;
+ memset(&info, 0, sizeof(info));
+ info.device = device;
+ int ret = ioctl(fd, SNDRV_CTL_IOCTL_RAWMIDI_INFO, &info);
+ close(fd);
+
+ if (ret < 0) {
+ ALOGE("SNDRV_CTL_IOCTL_RAWMIDI_INFO failed, errno: %d path: %s", errno, path);
+ return -1;
+ }
+
+ ALOGD("subdevices_count: %d", info.subdevices_count);
+ return info.subdevices_count;
+}
+
+static jobjectArray
+android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device,
+ jint subdevice_count)
+{
+ char path[100];
+
+ snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device);
+
+ jobjectArray fds = env->NewObjectArray(subdevice_count, sFileDescriptorClass, NULL);
+ if (!fds) {
+ return NULL;
+ }
+
+ // to support multiple subdevices we open the same file multiple times
+ for (int i = 0; i < subdevice_count; i++) {
+ int fd = open(path, O_RDWR);
+ if (fd < 0) {
+ ALOGE("open failed on %s for index %d", path, i);
+ return NULL;
+ }
+
+ jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
+ env->SetObjectArrayElement(fds, i, fileDescriptor);
+ env->DeleteLocalRef(fileDescriptor);
+ }
+
+ return fds;
+}
+
+static JNINativeMethod method_table[] = {
+ { "nativeGetSubdeviceCount", "(II)I", (void*)android_server_UsbMidiDevice_get_subdevice_count },
+ { "nativeOpen", "(III)[Ljava/io/FileDescriptor;", (void*)android_server_UsbMidiDevice_open },
+};
+
+int register_android_server_UsbMidiDevice(JNIEnv *env)
+{
+ jclass clazz = env->FindClass("java/io/FileDescriptor");
+ if (clazz == NULL) {
+ ALOGE("Can't find java/io/FileDescriptor");
+ return -1;
+ }
+ sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);;
+
+ clazz = env->FindClass("com/android/server/usb/UsbMidiDevice");
+ if (clazz == NULL) {
+ ALOGE("Can't find com/android/server/usb/UsbMidiDevice");
+ return -1;
+ }
+
+ return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice",
+ method_table, NELEM(method_table));
+}
+
+};
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c65b3be..7db7414 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -32,6 +32,7 @@
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
+int register_android_server_UsbMidiDevice(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
@@ -65,6 +66,7 @@
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
register_android_server_UsbDeviceManager(env);
+ register_android_server_UsbMidiDevice(env);
register_android_server_UsbHostManager(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index eca398c..7f9af31 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -74,7 +74,7 @@
import com.android.server.media.MediaRouterService;
import com.android.server.media.MediaSessionService;
import com.android.server.media.projection.MediaProjectionManagerService;
-import com.android.server.midi.MidiService;
+import com.android.server.MidiService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
import com.android.server.notification.NotificationManagerService;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 32ca723..6589135 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -24,15 +24,15 @@
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbInterface;
import android.media.AudioManager;
-import android.midi.IMidiManager;
import android.os.FileObserver;
import android.os.IBinder;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
+import libcore.io.IoUtils;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -42,14 +42,13 @@
/**
* UsbAlsaManager manages USB audio and MIDI devices.
*/
-public class UsbAlsaManager {
+public final class UsbAlsaManager {
private static final String TAG = UsbAlsaManager.class.getSimpleName();
private static final boolean DEBUG = true;
private static final String ALSA_DIRECTORY = "/dev/snd/";
private final Context mContext;
- private IMidiManager mMidiManager;
private final AlsaCardsParser mCardsParser = new AlsaCardsParser();
private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser();
@@ -60,6 +59,9 @@
private final HashMap<UsbDevice,UsbAudioDevice>
mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>();
+ private final HashMap<UsbDevice,UsbMidiDevice>
+ mMidiDevices = new HashMap<UsbDevice,UsbMidiDevice>();
+
private final HashMap<String,AlsaDevice>
mAlsaDevices = new HashMap<String,AlsaDevice>();
@@ -121,8 +123,6 @@
}
public void systemReady() {
- final IBinder b = ServiceManager.getService(Context.MIDI_SERVICE);
- mMidiManager = IMidiManager.Stub.asInterface(b);
mAlsaObserver.startWatching();
// add existing alsa devices
@@ -151,7 +151,6 @@
intent.putExtra("device", audioDevice.mDevice);
intent.putExtra("hasPlayback", audioDevice.mHasPlayback);
intent.putExtra("hasCapture", audioDevice.mHasCapture);
- intent.putExtra("hasMIDI", audioDevice.mHasMIDI);
intent.putExtra("class", audioDevice.mDeviceClass);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
@@ -235,9 +234,9 @@
/*
* Select the default device of the specified card.
*/
- /* package */ boolean selectCard(int card) {
+ /* package */ boolean selectAudioCard(int card) {
if (DEBUG) {
- Slog.d(TAG, "selectCard() card:" + card);
+ Slog.d(TAG, "selectAudioCard() card:" + card);
}
if (!mCardsParser.isCardUsb(card)) {
// Don't. AudioPolicyManager has logic for falling back to internal devices.
@@ -259,7 +258,6 @@
boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
- boolean hasMidi = mDevicesParser.hasMIDIDevices(card);
int deviceClass =
(mCardsParser.isCardUsb(card)
? UsbAudioDevice.kAudioDeviceClass_External
@@ -275,21 +273,13 @@
if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) {
return false;
}
- //TODO - seems to me that we need to decouple the above tests for audio
- // from the one below for MIDI.
-
- // MIDI device file needed/present?
- AlsaDevice midiDevice = null;
- if (hasMidi) {
- midiDevice = waitForAlsaDevice(card, device, AlsaDevice.TYPE_MIDI);
- }
if (DEBUG) {
Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
}
mSelectedAudioDevice =
- new UsbAudioDevice(card, device, hasPlayback, hasCapture, hasMidi, deviceClass);
+ new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
mSelectedAudioDevice.mDeviceName = mCardsParser.getCardRecordFor(card).mCardName;
mSelectedAudioDevice.mDeviceDescription =
mCardsParser.getCardRecordFor(card).mCardDescription;
@@ -304,7 +294,7 @@
Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
}
mCardsParser.scan();
- return selectCard(mCardsParser.getDefaultCard());
+ return selectAudioCard(mCardsParser.getDefaultCard());
}
/* package */ void deviceAdded(UsbDevice usbDevice) {
@@ -314,7 +304,6 @@
// Is there an audio interface in there?
boolean isAudioDevice = false;
- AlsaDevice midiDevice = null;
// FIXME - handle multiple configurations?
int interfaceCount = usbDevice.getInterfaceCount();
@@ -347,36 +336,44 @@
// If the default isn't a USB device, let the existing "select internal mechanism"
// handle the selection.
if (mCardsParser.isCardUsb(addedCard)) {
- selectCard(addedCard);
+ selectAudioCard(addedCard);
mAudioDevices.put(usbDevice, mSelectedAudioDevice);
- }
- if (midiDevice != null && mMidiManager != null) {
- try {
- mMidiManager.alsaDeviceAdded(midiDevice.mCard, midiDevice.mDevice, usbDevice);
- } catch (RemoteException e) {
- Slog.e(TAG, "MIDI Manager dead", e);
+ // look for MIDI devices
+
+ // Don't need to call mDevicesParser.scan() because selectAudioCard() does this above.
+ // Uncomment this next line if that behavior changes in the fugure.
+ // mDevicesParser.scan()
+
+ boolean hasMidi = mDevicesParser.hasMIDIDevices(addedCard);
+ if (hasMidi) {
+ int device = mDevicesParser.getDefaultDeviceNum(addedCard);
+ AlsaDevice alsaDevice = waitForAlsaDevice(addedCard, device, AlsaDevice.TYPE_MIDI);
+ if (alsaDevice != null) {
+ UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, usbDevice,
+ alsaDevice.mCard, alsaDevice.mDevice);
+ if (usbMidiDevice != null) {
+ mMidiDevices.put(usbDevice, usbMidiDevice);
+ }
+ }
}
}
}
- /* package */ void deviceRemoved(UsbDevice device) {
+ /* package */ void deviceRemoved(UsbDevice usbDevice) {
if (DEBUG) {
- Slog.d(TAG, "deviceRemoved(): " + device);
+ Slog.d(TAG, "deviceRemoved(): " + usbDevice);
}
- UsbAudioDevice audioDevice = mAudioDevices.remove(device);
+ UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice);
if (audioDevice != null) {
if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) {
sendDeviceNotification(audioDevice, false);
}
- if (audioDevice.mHasMIDI) {
- try {
- mMidiManager.alsaDeviceRemoved(device);
- } catch (RemoteException e) {
- Slog.e(TAG, "MIDI Manager dead", e);
- }
- }
+ }
+ UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice);
+ if (usbMidiDevice != null) {
+ IoUtils.closeQuietly(usbMidiDevice);
}
mSelectedAudioDevice = null;
@@ -400,10 +397,14 @@
// Logging
//
public void dump(FileDescriptor fd, PrintWriter pw) {
- pw.println(" USB AudioDevices:");
+ pw.println(" USB Audio Devices:");
for (UsbDevice device : mAudioDevices.keySet()) {
pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device));
}
+ pw.println(" USB MIDI Devices:");
+ for (UsbDevice device : mMidiDevices.keySet()) {
+ pw.println(" " + device.getDeviceName() + ": " + mMidiDevices.get(device));
+ }
}
public void logDevicesList(String title) {
diff --git a/services/usb/java/com/android/server/usb/UsbAudioDevice.java b/services/usb/java/com/android/server/usb/UsbAudioDevice.java
index b7b9563..069d917 100644
--- a/services/usb/java/com/android/server/usb/UsbAudioDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAudioDevice.java
@@ -24,7 +24,6 @@
public int mDevice;
public boolean mHasPlayback;
public boolean mHasCapture;
- public boolean mHasMIDI;
// Device "class" flags
public static final int kAudioDeviceClassMask = 0x00FFFFFF;
@@ -41,12 +40,11 @@
public String mDeviceDescription = "";
public UsbAudioDevice(int card, int device,
- boolean hasPlayback, boolean hasCapture, boolean hasMidi, int deviceClass) {
+ boolean hasPlayback, boolean hasCapture, int deviceClass) {
mCard = card;
mDevice = device;
mHasPlayback = hasPlayback;
mHasCapture = hasCapture;
- mHasMIDI = hasMidi;
mDeviceClass = deviceClass;
}
@@ -58,7 +56,6 @@
sb.append(", description: " + mDeviceDescription);
sb.append(", hasPlayback: " + mHasPlayback);
sb.append(", hasCapture: " + mHasCapture);
- sb.append(", hasMidi: " + mHasMIDI);
sb.append(", class: 0x" + Integer.toHexString(mDeviceClass) + "]");
return sb.toString();
}
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
new file mode 100644
index 0000000..aaac331
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.
+ * See the License for the specific language governing permissions an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+import android.midi.MidiDeviceInfo;
+import android.midi.MidiDeviceServer;
+import android.midi.MidiManager;
+import android.midi.MidiReceiver;
+import android.midi.MidiSender;
+import android.midi.MidiUtils;
+import android.os.Bundle;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructPollfd;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public final class UsbMidiDevice implements Closeable {
+ private static final String TAG = "UsbMidiDevice";
+
+ private final MidiDeviceServer mServer;
+ private final MidiReceiver[] mOutputPortReceivers;
+
+ // for polling multiple FileDescriptors for MIDI events
+ private final StructPollfd[] mPollFDs;
+ private final FileInputStream[] mInputStreams;
+ private final FileOutputStream[] mOutputStreams;
+
+ public static UsbMidiDevice create(Context context, UsbDevice usbDevice, int card, int device) {
+ MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+ if (midiManager == null) {
+ Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
+ return null;
+ }
+
+ // FIXME - support devices with different number of input and output ports
+ int subDevices = nativeGetSubdeviceCount(card, device);
+ if (subDevices <= 0) {
+ Log.e(TAG, "nativeGetSubdeviceCount failed");
+ return null;
+ }
+
+ // FIXME - support devices with different number of input and output ports
+ FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices);
+ if (fileDescriptors == null) {
+ Log.e(TAG, "nativeOpen failed");
+ return null;
+ }
+
+ Bundle properties = new Bundle();
+ properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, usbDevice.getManufacturerName());
+ properties.putString(MidiDeviceInfo.PROPERTY_MODEL, usbDevice.getProductName());
+ properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER, usbDevice.getSerialNumber());
+ properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
+ properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
+ MidiDeviceServer server = midiManager.createDeviceServer(subDevices, subDevices, properties,
+ false, MidiDeviceInfo.TYPE_USB);
+ if (server == null) {
+ Log.e(TAG, "createDeviceServer failed");
+ return null;
+ }
+
+ return new UsbMidiDevice(server, fileDescriptors, fileDescriptors);
+ }
+
+ private UsbMidiDevice(MidiDeviceServer server, FileDescriptor[] inputFiles,
+ FileDescriptor[] outputFiles) {
+ mServer = server;
+ int inputCount = inputFiles.length;
+ int outputCount = outputFiles.length;
+
+ mPollFDs = new StructPollfd[inputCount];
+ mInputStreams = new FileInputStream[inputCount];
+ for (int i = 0; i < inputCount; i++) {
+ FileDescriptor fd = inputFiles[i];
+ StructPollfd pollfd = new StructPollfd();
+ pollfd.fd = fd;
+ pollfd.events = (short)OsConstants.POLLIN;
+ mPollFDs[i] = pollfd;
+ mInputStreams[i] = new FileInputStream(fd);
+ }
+
+ mOutputStreams = new FileOutputStream[outputCount];
+ for (int i = 0; i < outputCount; i++) {
+ mOutputStreams[i] = new FileOutputStream(outputFiles[i]);
+ }
+
+ mOutputPortReceivers = new MidiReceiver[outputCount];
+ for (int port = 0; port < outputCount; port++) {
+ mOutputPortReceivers[port] = server.openOutputPortReceiver(port);
+ }
+
+ for (int port = 0; port < inputCount; port++) {
+ final int portNumberF = port;
+ MidiReceiver receiver = new MidiReceiver() {
+
+ @Override
+ public void onPost(byte[] data, int offset, int count, long timestamp)
+ throws IOException {
+ // FIXME - timestamps are ignored, future posting not supported yet.
+ mOutputStreams[portNumberF].write(data, offset, count);
+ }
+ };
+ MidiSender sender = server.openInputPortSender(port);
+ sender.connect(receiver);
+ }
+
+ new Thread() {
+ @Override
+ public void run() {
+ byte[] buffer = new byte[3];
+ try {
+ while (true) {
+ // look for a readable FileDescriptor
+ for (int index = 0; index < mPollFDs.length; index++) {
+ StructPollfd pfd = mPollFDs[index];
+ if ((pfd.revents & OsConstants.POLLIN) != 0) {
+ // clear readable flag
+ pfd.revents = 0;
+ int count = readMessage(buffer, index);
+ mOutputPortReceivers[index].onPost(buffer, 0, count, System.nanoTime());
+ }
+ }
+
+ // poll if none are readable
+ Os.poll(mPollFDs, -1 /* infinite timeout */);
+ }
+ } catch (IOException e) {
+ Log.d(TAG, "reader thread exiting");
+ } catch (ErrnoException e) {
+ Log.d(TAG, "reader thread exiting");
+ }
+ }
+ }.start();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mServer.close();
+
+ for (int i = 0; i < mInputStreams.length; i++) {
+ mInputStreams[i].close();
+ }
+ for (int i = 0; i < mOutputStreams.length; i++) {
+ mOutputStreams[i].close();
+ }
+ }
+
+ private int readMessage(byte[] buffer, int index) throws IOException {
+ FileInputStream inputStream = mInputStreams[index];
+
+ if (inputStream.read(buffer, 0, 1) != 1) {
+ Log.e(TAG, "could not read command byte");
+ return -1;
+ }
+ int dataSize = MidiUtils.getMessageDataSize(buffer[0]);
+ if (dataSize < 0) {
+ return -1;
+ }
+ if (dataSize > 0) {
+ if (inputStream.read(buffer, 1, dataSize) != dataSize) {
+ Log.e(TAG, "could not read command data");
+ return -1;
+ }
+ }
+ return dataSize + 1;
+ }
+
+ private static native int nativeGetSubdeviceCount(int card, int device);
+ private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
+}