Loading services/core/jni/com_android_server_UsbMidiDevice.cpp +26 −4 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ namespace android { static jclass sFileDescriptorClass; static jfieldID sPipeFDField; static jint android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */, Loading Loading @@ -66,14 +67,15 @@ android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */ } static jobjectArray android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device, 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); // allocate one extra file descriptor for close pipe jobjectArray fds = env->NewObjectArray(subdevice_count + 1, sFileDescriptorClass, NULL); if (!fds) { return NULL; } Loading @@ -91,12 +93,27 @@ android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, ji env->DeleteLocalRef(fileDescriptor); } // create a pipe to use for unblocking our input thread int pipeFD[2]; pipe(pipeFD); jobject fileDescriptor = jniCreateFileDescriptor(env, pipeFD[0]); env->SetObjectArrayElement(fds, subdevice_count, fileDescriptor); env->DeleteLocalRef(fileDescriptor); // store our end of the pipe in mPipeFD env->SetIntField(thiz, sPipeFDField, pipeFD[1]); return fds; } static void android_server_UsbMidiDevice_close(JNIEnv *env, jobject /* thiz */, jobjectArray fds) android_server_UsbMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds) { // write to mPipeFD to unblock input thread jint pipeFD = env->GetIntField(thiz, sPipeFDField); write(pipeFD, &pipeFD, sizeof(pipeFD)); close(pipeFD); env->SetIntField(thiz, sPipeFDField, -1); int count = env->GetArrayLength(fds); for (int i = 0; i < count; i++) { jobject fd = env->GetObjectArrayElement(fds, i); Loading @@ -117,13 +134,18 @@ int register_android_server_UsbMidiDevice(JNIEnv *env) ALOGE("Can't find java/io/FileDescriptor"); return -1; } sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);; 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; } sPipeFDField = env->GetFieldID(clazz, "mPipeFD", "I"); if (sPipeFDField == NULL) { ALOGE("Can't find UsbMidiDevice.mPipeFD"); return -1; } return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice", method_table, NELEM(method_table)); Loading services/usb/java/com/android/server/usb/UsbMidiDevice.java +170 −59 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.usb; import android.content.Context; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceServer; import android.media.midi.MidiDeviceStatus; import android.media.midi.MidiManager; import android.media.midi.MidiReceiver; import android.media.midi.MidiSender; Loading @@ -43,38 +44,100 @@ import java.io.IOException; public final class UsbMidiDevice implements Closeable { private static final String TAG = "UsbMidiDevice"; private final int mAlsaCard; private final int mAlsaDevice; private final int mSubdeviceCount; private final InputReceiverProxy[] mInputPortReceivers; private MidiDeviceServer mServer; // event schedulers for each output port private final MidiEventScheduler[] mEventSchedulers; private MidiEventScheduler[] mEventSchedulers; private static final int BUFFER_SIZE = 512; private final FileDescriptor[] mFileDescriptors; private FileDescriptor[] mFileDescriptors; // for polling multiple FileDescriptors for MIDI events private final StructPollfd[] mPollFDs; private StructPollfd[] mPollFDs; // streams for reading from ALSA driver private final FileInputStream[] mInputStreams; private FileInputStream[] mInputStreams; // streams for writing to ALSA driver private final FileOutputStream[] mOutputStreams; private FileOutputStream[] mOutputStreams; public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { // 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; private final Object mLock = new Object(); private boolean mIsOpen; // pipe file descriptor for signalling input thread to exit // only accessed from JNI code private int mPipeFD = -1; private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { @Override public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { MidiDeviceInfo deviceInfo = status.getDeviceInfo(); int inputPorts = deviceInfo.getInputPortCount(); int outputPorts = deviceInfo.getOutputPortCount(); boolean hasOpenPorts = false; for (int i = 0; i < inputPorts; i++) { if (status.isInputPortOpen(i)) { hasOpenPorts = true; break; } } if (!hasOpenPorts) { for (int i = 0; i < outputPorts; i++) { if (status.getOutputPortOpenCount(i) > 0) { hasOpenPorts = true; break; } } } synchronized (mLock) { if (hasOpenPorts && !mIsOpen) { openLocked(); } else if (!hasOpenPorts && mIsOpen) { closeLocked(); } } } @Override public void onClose() { } }; // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist // until the device has active clients private final class InputReceiverProxy extends MidiReceiver { private MidiReceiver mReceiver; @Override public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException { MidiReceiver receiver = mReceiver; if (receiver != null) { receiver.send(msg, offset, count, timestamp); } } public void setReceiver(MidiReceiver receiver) { mReceiver = receiver; } } public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { // 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"); int subDeviceCount = nativeGetSubdeviceCount(card, device); if (subDeviceCount <= 0) { Log.e(TAG, "nativeGetSubdeviceCount failed"); return null; } UsbMidiDevice midiDevice = new UsbMidiDevice(fileDescriptors); UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount); if (!midiDevice.register(context, properties)) { IoUtils.closeQuietly(midiDevice); Log.e(TAG, "createDeviceServer failed"); Loading @@ -83,10 +146,32 @@ public final class UsbMidiDevice implements Closeable { return midiDevice; } private UsbMidiDevice(FileDescriptor[] fileDescriptors) { private UsbMidiDevice(int card, int device, int subdeviceCount) { mAlsaCard = card; mAlsaDevice = device; mSubdeviceCount = subdeviceCount; // FIXME - support devices with different number of input and output ports int inputCount = subdeviceCount; mInputPortReceivers = new InputReceiverProxy[inputCount]; for (int port = 0; port < inputCount; port++) { mInputPortReceivers[port] = new InputReceiverProxy(); } } private boolean openLocked() { // FIXME - support devices with different number of input and output ports FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount); if (fileDescriptors == null) { Log.e(TAG, "nativeOpen failed"); return false; } mFileDescriptors = fileDescriptors; int inputCount = fileDescriptors.length; int outputCount = fileDescriptors.length; // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll() // in our input thread int outputCount = fileDescriptors.length - 1; mPollFDs = new StructPollfd[inputCount]; mInputStreams = new FileInputStream[inputCount]; Loading @@ -103,29 +188,12 @@ public final class UsbMidiDevice implements Closeable { mEventSchedulers = new MidiEventScheduler[outputCount]; for (int i = 0; i < outputCount; i++) { mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); mEventSchedulers[i] = new MidiEventScheduler(); } } private boolean register(Context context, Bundle properties) { MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); if (midiManager == null) { Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); return false; } int inputCount = mInputStreams.length; int outputCount = mOutputStreams.length; MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount]; for (int port = 0; port < inputCount; port++) { inputPortReceivers[port] = mEventSchedulers[port].getReceiver(); MidiEventScheduler scheduler = new MidiEventScheduler(); mEventSchedulers[i] = scheduler; mInputPortReceivers[i].setReceiver(scheduler.getReceiver()); } mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount, null, null, properties, MidiDeviceInfo.TYPE_USB, null); if (mServer == null) { return false; } final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); // Create input thread which will read from all input ports Loading @@ -134,24 +202,32 @@ public final class UsbMidiDevice implements Closeable { public void run() { byte[] buffer = new byte[BUFFER_SIZE]; try { boolean done = false; while (!done) { while (true) { synchronized (mLock) { if (!mIsOpen) break; // look for a readable FileDescriptor for (int index = 0; index < mPollFDs.length; index++) { StructPollfd pfd = mPollFDs[index]; if ((pfd.revents & OsConstants.POLLIN) != 0) { if ((pfd.revents & (OsConstants.POLLERR | OsConstants.POLLHUP)) != 0) { break; } else if ((pfd.revents & OsConstants.POLLIN) != 0) { // clear readable flag pfd.revents = 0; if (index == mInputStreams.length - 1) { // last file descriptor is used only for unblocking Os.poll() break; } int count = mInputStreams[index].read(buffer); outputReceivers[index].send(buffer, 0, count); } else if ((pfd.revents & (OsConstants.POLLERR | OsConstants.POLLHUP)) != 0) { done = true; } } } // wait until we have a readable port // wait until we have a readable port or we are signalled to close Os.poll(mPollFDs, -1 /* infinite timeout */); } } catch (IOException e) { Loading Loading @@ -195,29 +271,64 @@ public final class UsbMidiDevice implements Closeable { }.start(); } mIsOpen = true; return true; } private boolean register(Context context, Bundle properties) { MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); if (midiManager == null) { Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); return false; } mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount, null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback); if (mServer == null) { return false; } return true; } @Override public void close() throws IOException { for (int i = 0; i < mEventSchedulers.length; i++) { mEventSchedulers[i].close(); synchronized (mLock) { if (mIsOpen) { closeLocked(); } } if (mServer != null) { mServer.close(); IoUtils.closeQuietly(mServer); } } private void closeLocked() { for (int i = 0; i < mEventSchedulers.length; i++) { mInputPortReceivers[i].setReceiver(null); mEventSchedulers[i].close(); } mEventSchedulers = null; for (int i = 0; i < mInputStreams.length; i++) { mInputStreams[i].close(); IoUtils.closeQuietly(mInputStreams[i]); } mInputStreams = null; for (int i = 0; i < mOutputStreams.length; i++) { mOutputStreams[i].close(); IoUtils.closeQuietly(mOutputStreams[i]); } mOutputStreams = null; // nativeClose will close the file descriptors and signal the input thread to exit nativeClose(mFileDescriptors); mFileDescriptors = null; mIsOpen = false; } private static native int nativeGetSubdeviceCount(int card, int device); private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); private static native void nativeClose(FileDescriptor[] fileDescriptors); private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); private native void nativeClose(FileDescriptor[] fileDescriptors); } Loading
services/core/jni/com_android_server_UsbMidiDevice.cpp +26 −4 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ namespace android { static jclass sFileDescriptorClass; static jfieldID sPipeFDField; static jint android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */, Loading Loading @@ -66,14 +67,15 @@ android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */ } static jobjectArray android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device, 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); // allocate one extra file descriptor for close pipe jobjectArray fds = env->NewObjectArray(subdevice_count + 1, sFileDescriptorClass, NULL); if (!fds) { return NULL; } Loading @@ -91,12 +93,27 @@ android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, ji env->DeleteLocalRef(fileDescriptor); } // create a pipe to use for unblocking our input thread int pipeFD[2]; pipe(pipeFD); jobject fileDescriptor = jniCreateFileDescriptor(env, pipeFD[0]); env->SetObjectArrayElement(fds, subdevice_count, fileDescriptor); env->DeleteLocalRef(fileDescriptor); // store our end of the pipe in mPipeFD env->SetIntField(thiz, sPipeFDField, pipeFD[1]); return fds; } static void android_server_UsbMidiDevice_close(JNIEnv *env, jobject /* thiz */, jobjectArray fds) android_server_UsbMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds) { // write to mPipeFD to unblock input thread jint pipeFD = env->GetIntField(thiz, sPipeFDField); write(pipeFD, &pipeFD, sizeof(pipeFD)); close(pipeFD); env->SetIntField(thiz, sPipeFDField, -1); int count = env->GetArrayLength(fds); for (int i = 0; i < count; i++) { jobject fd = env->GetObjectArrayElement(fds, i); Loading @@ -117,13 +134,18 @@ int register_android_server_UsbMidiDevice(JNIEnv *env) ALOGE("Can't find java/io/FileDescriptor"); return -1; } sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);; 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; } sPipeFDField = env->GetFieldID(clazz, "mPipeFD", "I"); if (sPipeFDField == NULL) { ALOGE("Can't find UsbMidiDevice.mPipeFD"); return -1; } return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice", method_table, NELEM(method_table)); Loading
services/usb/java/com/android/server/usb/UsbMidiDevice.java +170 −59 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.usb; import android.content.Context; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceServer; import android.media.midi.MidiDeviceStatus; import android.media.midi.MidiManager; import android.media.midi.MidiReceiver; import android.media.midi.MidiSender; Loading @@ -43,38 +44,100 @@ import java.io.IOException; public final class UsbMidiDevice implements Closeable { private static final String TAG = "UsbMidiDevice"; private final int mAlsaCard; private final int mAlsaDevice; private final int mSubdeviceCount; private final InputReceiverProxy[] mInputPortReceivers; private MidiDeviceServer mServer; // event schedulers for each output port private final MidiEventScheduler[] mEventSchedulers; private MidiEventScheduler[] mEventSchedulers; private static final int BUFFER_SIZE = 512; private final FileDescriptor[] mFileDescriptors; private FileDescriptor[] mFileDescriptors; // for polling multiple FileDescriptors for MIDI events private final StructPollfd[] mPollFDs; private StructPollfd[] mPollFDs; // streams for reading from ALSA driver private final FileInputStream[] mInputStreams; private FileInputStream[] mInputStreams; // streams for writing to ALSA driver private final FileOutputStream[] mOutputStreams; private FileOutputStream[] mOutputStreams; public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { // 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; private final Object mLock = new Object(); private boolean mIsOpen; // pipe file descriptor for signalling input thread to exit // only accessed from JNI code private int mPipeFD = -1; private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { @Override public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { MidiDeviceInfo deviceInfo = status.getDeviceInfo(); int inputPorts = deviceInfo.getInputPortCount(); int outputPorts = deviceInfo.getOutputPortCount(); boolean hasOpenPorts = false; for (int i = 0; i < inputPorts; i++) { if (status.isInputPortOpen(i)) { hasOpenPorts = true; break; } } if (!hasOpenPorts) { for (int i = 0; i < outputPorts; i++) { if (status.getOutputPortOpenCount(i) > 0) { hasOpenPorts = true; break; } } } synchronized (mLock) { if (hasOpenPorts && !mIsOpen) { openLocked(); } else if (!hasOpenPorts && mIsOpen) { closeLocked(); } } } @Override public void onClose() { } }; // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist // until the device has active clients private final class InputReceiverProxy extends MidiReceiver { private MidiReceiver mReceiver; @Override public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException { MidiReceiver receiver = mReceiver; if (receiver != null) { receiver.send(msg, offset, count, timestamp); } } public void setReceiver(MidiReceiver receiver) { mReceiver = receiver; } } public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { // 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"); int subDeviceCount = nativeGetSubdeviceCount(card, device); if (subDeviceCount <= 0) { Log.e(TAG, "nativeGetSubdeviceCount failed"); return null; } UsbMidiDevice midiDevice = new UsbMidiDevice(fileDescriptors); UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount); if (!midiDevice.register(context, properties)) { IoUtils.closeQuietly(midiDevice); Log.e(TAG, "createDeviceServer failed"); Loading @@ -83,10 +146,32 @@ public final class UsbMidiDevice implements Closeable { return midiDevice; } private UsbMidiDevice(FileDescriptor[] fileDescriptors) { private UsbMidiDevice(int card, int device, int subdeviceCount) { mAlsaCard = card; mAlsaDevice = device; mSubdeviceCount = subdeviceCount; // FIXME - support devices with different number of input and output ports int inputCount = subdeviceCount; mInputPortReceivers = new InputReceiverProxy[inputCount]; for (int port = 0; port < inputCount; port++) { mInputPortReceivers[port] = new InputReceiverProxy(); } } private boolean openLocked() { // FIXME - support devices with different number of input and output ports FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount); if (fileDescriptors == null) { Log.e(TAG, "nativeOpen failed"); return false; } mFileDescriptors = fileDescriptors; int inputCount = fileDescriptors.length; int outputCount = fileDescriptors.length; // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll() // in our input thread int outputCount = fileDescriptors.length - 1; mPollFDs = new StructPollfd[inputCount]; mInputStreams = new FileInputStream[inputCount]; Loading @@ -103,29 +188,12 @@ public final class UsbMidiDevice implements Closeable { mEventSchedulers = new MidiEventScheduler[outputCount]; for (int i = 0; i < outputCount; i++) { mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); mEventSchedulers[i] = new MidiEventScheduler(); } } private boolean register(Context context, Bundle properties) { MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); if (midiManager == null) { Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); return false; } int inputCount = mInputStreams.length; int outputCount = mOutputStreams.length; MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount]; for (int port = 0; port < inputCount; port++) { inputPortReceivers[port] = mEventSchedulers[port].getReceiver(); MidiEventScheduler scheduler = new MidiEventScheduler(); mEventSchedulers[i] = scheduler; mInputPortReceivers[i].setReceiver(scheduler.getReceiver()); } mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount, null, null, properties, MidiDeviceInfo.TYPE_USB, null); if (mServer == null) { return false; } final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); // Create input thread which will read from all input ports Loading @@ -134,24 +202,32 @@ public final class UsbMidiDevice implements Closeable { public void run() { byte[] buffer = new byte[BUFFER_SIZE]; try { boolean done = false; while (!done) { while (true) { synchronized (mLock) { if (!mIsOpen) break; // look for a readable FileDescriptor for (int index = 0; index < mPollFDs.length; index++) { StructPollfd pfd = mPollFDs[index]; if ((pfd.revents & OsConstants.POLLIN) != 0) { if ((pfd.revents & (OsConstants.POLLERR | OsConstants.POLLHUP)) != 0) { break; } else if ((pfd.revents & OsConstants.POLLIN) != 0) { // clear readable flag pfd.revents = 0; if (index == mInputStreams.length - 1) { // last file descriptor is used only for unblocking Os.poll() break; } int count = mInputStreams[index].read(buffer); outputReceivers[index].send(buffer, 0, count); } else if ((pfd.revents & (OsConstants.POLLERR | OsConstants.POLLHUP)) != 0) { done = true; } } } // wait until we have a readable port // wait until we have a readable port or we are signalled to close Os.poll(mPollFDs, -1 /* infinite timeout */); } } catch (IOException e) { Loading Loading @@ -195,29 +271,64 @@ public final class UsbMidiDevice implements Closeable { }.start(); } mIsOpen = true; return true; } private boolean register(Context context, Bundle properties) { MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); if (midiManager == null) { Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); return false; } mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount, null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback); if (mServer == null) { return false; } return true; } @Override public void close() throws IOException { for (int i = 0; i < mEventSchedulers.length; i++) { mEventSchedulers[i].close(); synchronized (mLock) { if (mIsOpen) { closeLocked(); } } if (mServer != null) { mServer.close(); IoUtils.closeQuietly(mServer); } } private void closeLocked() { for (int i = 0; i < mEventSchedulers.length; i++) { mInputPortReceivers[i].setReceiver(null); mEventSchedulers[i].close(); } mEventSchedulers = null; for (int i = 0; i < mInputStreams.length; i++) { mInputStreams[i].close(); IoUtils.closeQuietly(mInputStreams[i]); } mInputStreams = null; for (int i = 0; i < mOutputStreams.length; i++) { mOutputStreams[i].close(); IoUtils.closeQuietly(mOutputStreams[i]); } mOutputStreams = null; // nativeClose will close the file descriptors and signal the input thread to exit nativeClose(mFileDescriptors); mFileDescriptors = null; mIsOpen = false; } private static native int nativeGetSubdeviceCount(int card, int device); private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); private static native void nativeClose(FileDescriptor[] fileDescriptors); private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); private native void nativeClose(FileDescriptor[] fileDescriptors); }