| /* |
| * 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 <nativehelper/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 jfieldID sPipeFDField; |
| |
| static jint |
| android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */, |
| jint card, jint device) |
| { |
| char path[100]; |
| int fd; |
| const int kMaxRetries = 10; |
| const int kSleepMicroseconds = 2000; |
| |
| snprintf(path, sizeof(path), "/dev/snd/controlC%d", card); |
| // This control device may not have been created yet. So we should |
| // try to open it several times to prevent intermittent failure |
| // from a race condition. |
| int retryCounter = 0; |
| while ((fd = open(path, O_RDWR)) < 0) { |
| if (++retryCounter > kMaxRetries) { |
| ALOGE("timed out after %d tries, could not open %s", retryCounter, path); |
| return 0; |
| } else { |
| ALOGW("attempt #%d, could not open %s", retryCounter, path); |
| // Increase the sleep interval each time. |
| // 10 retries will total 2 * sum(1..10) = 110 milliseconds. |
| // Typically the device should be ready in 5-10 milliseconds. |
| usleep(kSleepMicroseconds * retryCounter); |
| } |
| } |
| |
| 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); |
| |
| // allocate one extra file descriptor for close pipe |
| jobjectArray fds = env->NewObjectArray(subdevice_count + 1, 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); |
| } |
| |
| // 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) |
| { |
| // 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); |
| close(jniGetFDFromFileDescriptor(env, fd)); |
| } |
| } |
| |
| static JNINativeMethod method_table[] = { |
| { "nativeGetSubdeviceCount", "(II)I", (void*)android_server_UsbMidiDevice_get_subdevice_count }, |
| { "nativeOpen", "(III)[Ljava/io/FileDescriptor;", (void*)android_server_UsbMidiDevice_open }, |
| { "nativeClose", "([Ljava/io/FileDescriptor;)V", (void*)android_server_UsbMidiDevice_close }, |
| }; |
| |
| 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; |
| } |
| 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)); |
| } |
| |
| }; |