1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "NativeMIDI"
18 
19 #include <poll.h>
20 #include <unistd.h>
21 
22 #include <binder/Binder.h>
23 #include <android_util_Binder.h>
24 #include <utils/Log.h>
25 
26 #include <core_jni_helpers.h>
27 
28 #include "android/media/midi/BpMidiDeviceServer.h"
29 #include "MidiDeviceInfo.h"
30 
31 #include "include/amidi/AMidi.h"
32 #include "amidi_internal.h"
33 
34 using namespace android::media::midi;
35 
36 using android::IBinder;
37 using android::BBinder;
38 using android::OK;
39 using android::sp;
40 using android::status_t;
41 using android::base::unique_fd;
42 using android::binder::Status;
43 
44 struct AMIDI_Port {
45     std::atomic_int     state;      // One of the port status constants below.
46     const AMidiDevice  *device;    // Points to the AMidiDevice associated with the port.
47     sp<IBinder>         binderToken;// The Binder token associated with the port.
48     unique_fd           ufd;        // The unique file descriptor associated with the port.
49 };
50 
51 /*
52  * Port Status Constants
53  */
54 enum {
55     MIDI_PORT_STATE_CLOSED = 0,
56     MIDI_PORT_STATE_OPEN_IDLE,
57     MIDI_PORT_STATE_OPEN_ACTIVE
58 };
59 
60 /*
61  * Port Type Constants
62  */
63 enum {
64     PORTTYPE_OUTPUT = 0,
65     PORTTYPE_INPUT = 1
66 };
67 
68 /* TRANSFER PACKET FORMAT (as defined in MidiPortImpl.java)
69  *
70  * Transfer packet format is as follows (see MidiOutputPort.mThread.run() to see decomposition):
71  * |oc|md|md| ......... |md|ts|ts|ts|ts|ts|ts|ts|ts|
72  *  ^ +--------------------+-----------------------+
73  *  |  ^                    ^
74  *  |  |                    |
75  *  |  |                    + timestamp (8 bytes)
76  *  |  |
77  *  |  + MIDI data bytes (numBytes bytes)
78  *  |
79  *  + OpCode (AMIDI_OPCODE_DATA)
80  *
81  *  NOTE: The socket pair is configured to use SOCK_SEQPACKET mode.
82  *  SOCK_SEQPACKET, for a sequenced-packet socket that is connection-oriented, preserves message
83  *  boundaries, and delivers messages in the order that they were sent.
84  *  So 'read()' always returns a whole message.
85  */
86 #define AMIDI_PACKET_SIZE       1024
87 #define AMIDI_PACKET_OVERHEAD   9
88 #define AMIDI_BUFFER_SIZE       (AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD)
89 
90 // JNI IDs (see android_media_midi.cpp)
91 namespace android { namespace midi {
92 //  MidiDevice Fields
93 extern jfieldID gFidMidiNativeHandle;         // MidiDevice.mNativeHandle
94 extern jfieldID gFidMidiDeviceServerBinder;   // MidiDevice.mDeviceServerBinder
95 extern jfieldID gFidMidiDeviceInfo;           // MidiDevice.mDeviceInfo
96 
97 //  MidiDeviceInfo Fields
98 extern jfieldID mFidMidiDeviceId;             // MidiDeviceInfo.mId
99 }}
100 using namespace android::midi;
101 
102 static std::mutex openMutex; // Ensure that the device can be connected just once to 1 thread
103 
104 //// Handy debugging function.
105 //static void AMIDI_logBuffer(const uint8_t *data, size_t numBytes) {
106 //    for (size_t index = 0; index < numBytes; index++) {
107 //      ALOGI("  data @%zu [0x%X]", index, data[index]);
108 //    }
109 //}
110 
111 /*
112  * Device Functions
113  */
114 /**
115  * Retrieves information for the native MIDI device.
116  *
117  * device           The Native API token for the device. This value is obtained from the
118  *                  AMidiDevice_fromJava().
119  * outDeviceInfoPtr Receives the associated device info.
120  *
121  * Returns AMEDIA_OK or a negative error code.
122  *  - AMEDIA_ERROR_INVALID_PARAMETER
123  *  AMEDIA_ERROR_UNKNOWN
124  */
AMIDI_getDeviceInfo(const AMidiDevice * device,AMidiDeviceInfo * outDeviceInfoPtr)125 static media_status_t AMIDI_getDeviceInfo(const AMidiDevice *device,
126         AMidiDeviceInfo *outDeviceInfoPtr) {
127     if (device == nullptr) {
128         return AMEDIA_ERROR_INVALID_PARAMETER;
129     }
130 
131     MidiDeviceInfo deviceInfo;
132     Status txResult = device->server->getDeviceInfo(&deviceInfo);
133     if (!txResult.isOk()) {
134         ALOGE("AMIDI_getDeviceInfo transaction error: %d", txResult.transactionError());
135         return AMEDIA_ERROR_UNKNOWN;
136     }
137 
138     outDeviceInfoPtr->type = deviceInfo.getType();
139     outDeviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size();
140     outDeviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size();
141 
142     return AMEDIA_OK;
143 }
144 
AMidiDevice_fromJava(JNIEnv * env,jobject j_midiDeviceObj,AMidiDevice ** devicePtrPtr)145 media_status_t AMIDI_API AMidiDevice_fromJava(JNIEnv *env, jobject j_midiDeviceObj,
146         AMidiDevice** devicePtrPtr)
147 {
148     if (j_midiDeviceObj == nullptr) {
149         ALOGE("AMidiDevice_fromJava() invalid MidiDevice object.");
150         return AMEDIA_ERROR_INVALID_OBJECT;
151     }
152 
153     {
154         std::lock_guard<std::mutex> guard(openMutex);
155 
156         long handle = env->GetLongField(j_midiDeviceObj, gFidMidiNativeHandle);
157         if (handle != 0) {
158             // Already opened by someone.
159             return AMEDIA_ERROR_INVALID_OBJECT;
160         }
161 
162         jobject serverBinderObj = env->GetObjectField(j_midiDeviceObj, gFidMidiDeviceServerBinder);
163         sp<IBinder> serverBinder = android::ibinderForJavaObject(env, serverBinderObj);
164         if (serverBinder.get() == nullptr) {
165             ALOGE("AMidiDevice_fromJava couldn't connect to native MIDI server.");
166             return AMEDIA_ERROR_UNKNOWN;
167         }
168 
169         // don't check allocation failures, just abort..
170         AMidiDevice* devicePtr = new AMidiDevice;
171         devicePtr->server = new BpMidiDeviceServer(serverBinder);
172         jobject midiDeviceInfoObj = env->GetObjectField(j_midiDeviceObj, gFidMidiDeviceInfo);
173         devicePtr->deviceId = env->GetIntField(midiDeviceInfoObj, mFidMidiDeviceId);
174 
175         // Synchronize with the associated Java MidiDevice.
176         env->SetLongField(j_midiDeviceObj, gFidMidiNativeHandle, (long)devicePtr);
177         env->GetJavaVM(&devicePtr->javaVM);
178         devicePtr->midiDeviceObj = env->NewGlobalRef(j_midiDeviceObj);
179 
180         if (AMIDI_getDeviceInfo(devicePtr, &devicePtr->deviceInfo) != AMEDIA_OK) {
181             // This is weird, but maybe not fatal?
182             ALOGE("AMidiDevice_fromJava couldn't retrieve attributes of native device.");
183         }
184 
185         *devicePtrPtr = devicePtr;
186     }
187 
188     return AMEDIA_OK;
189 }
190 
AMidiDevice_release(const AMidiDevice * device)191 media_status_t AMIDI_API AMidiDevice_release(const AMidiDevice *device)
192 {
193     if (device == nullptr || device->midiDeviceObj == nullptr) {
194         return AMEDIA_ERROR_INVALID_PARAMETER;
195     }
196 
197     JNIEnv* env;
198     jint err = device->javaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
199     LOG_ALWAYS_FATAL_IF(err != JNI_OK, "AMidiDevice_release Error accessing JNIEnv err:%d", err);
200 
201     // Synchronize with the associated Java MidiDevice.
202     {
203         std::lock_guard<std::mutex> guard(openMutex);
204         long handle = env->GetLongField(device->midiDeviceObj, gFidMidiNativeHandle);
205         if (handle == 0) {
206             // Not opened as native.
207             ALOGE("AMidiDevice_release() device not opened in native client.");
208             return AMEDIA_ERROR_INVALID_OBJECT;
209         }
210 
211         env->SetLongField(device->midiDeviceObj, gFidMidiNativeHandle, 0L);
212     }
213     env->DeleteGlobalRef(device->midiDeviceObj);
214 
215     delete device;
216 
217     return AMEDIA_OK;
218 }
219 
AMidiDevice_getType(const AMidiDevice * device)220 int32_t AMIDI_API AMidiDevice_getType(const AMidiDevice *device) {
221     if (device == nullptr) {
222         return AMEDIA_ERROR_INVALID_PARAMETER;
223     }
224     return device->deviceInfo.type;
225 }
226 
AMidiDevice_getNumInputPorts(const AMidiDevice * device)227 ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) {
228     if (device == nullptr) {
229         return AMEDIA_ERROR_INVALID_PARAMETER;
230     }
231     return device->deviceInfo.inputPortCount;
232 }
233 
AMidiDevice_getNumOutputPorts(const AMidiDevice * device)234 ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) {
235     if (device == nullptr) {
236         return AMEDIA_ERROR_INVALID_PARAMETER;
237     }
238     return device->deviceInfo.outputPortCount;
239 }
240 
241 /*
242  * Port Helpers
243  */
AMIDI_openPort(const AMidiDevice * device,int32_t portNumber,int type,AMIDI_Port ** portPtr)244 static media_status_t AMIDI_openPort(const AMidiDevice *device, int32_t portNumber, int type,
245         AMIDI_Port **portPtr) {
246     if (device == nullptr) {
247         return AMEDIA_ERROR_INVALID_PARAMETER;
248     }
249 
250     sp<BBinder> portToken(new BBinder());
251     unique_fd ufd;
252     Status txResult = type == PORTTYPE_OUTPUT
253             ? device->server->openOutputPort(portToken, portNumber, &ufd)
254             : device->server->openInputPort(portToken, portNumber, &ufd);
255     if (!txResult.isOk()) {
256         ALOGE("AMIDI_openPort transaction error: %d", txResult.transactionError());
257         return AMEDIA_ERROR_UNKNOWN;
258     }
259 
260     AMIDI_Port *port = new AMIDI_Port;
261     port->state = MIDI_PORT_STATE_OPEN_IDLE;
262     port->device = device;
263     port->binderToken = portToken;
264     port->ufd = std::move(ufd);
265 
266     *portPtr = port;
267 
268     return AMEDIA_OK;
269 }
270 
AMIDI_closePort(AMIDI_Port * port)271 static void AMIDI_closePort(AMIDI_Port *port) {
272     if (port == nullptr) {
273         return;
274     }
275 
276     int portState = MIDI_PORT_STATE_OPEN_IDLE;
277     while (!port->state.compare_exchange_weak(portState, MIDI_PORT_STATE_CLOSED)) {
278         if (portState == MIDI_PORT_STATE_CLOSED) {
279             return; // Already closed
280         }
281     }
282 
283     Status txResult = port->device->server->closePort(port->binderToken);
284     if (!txResult.isOk()) {
285         ALOGE("Transaction error closing MIDI port:%d", txResult.transactionError());
286     }
287 
288     delete port;
289 }
290 
291 /*
292  * Output (receiving) API
293  */
AMidiOutputPort_open(const AMidiDevice * device,int32_t portNumber,AMidiOutputPort ** outOutputPortPtr)294 media_status_t AMIDI_API AMidiOutputPort_open(const AMidiDevice *device, int32_t portNumber,
295         AMidiOutputPort **outOutputPortPtr) {
296     return AMIDI_openPort(device, portNumber, PORTTYPE_OUTPUT, (AMIDI_Port**)outOutputPortPtr);
297 }
298 
299 /*
300  *  A little RAII (https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization)
301  *  class to ensure that the port state is correct irrespective of errors.
302  */
303 class MidiReceiver {
304 public:
MidiReceiver(AMIDI_Port * port)305     MidiReceiver(AMIDI_Port *port) : mPort(port) {}
306 
~MidiReceiver()307     ~MidiReceiver() {
308         // flag the port state to idle
309         mPort->state.store(MIDI_PORT_STATE_OPEN_IDLE);
310     }
311 
receive(int32_t * opcodePtr,uint8_t * buffer,size_t maxBytes,size_t * numBytesReceivedPtr,int64_t * timestampPtr)312     ssize_t receive(int32_t *opcodePtr, uint8_t *buffer, size_t maxBytes,
313             size_t *numBytesReceivedPtr, int64_t *timestampPtr) {
314         int portState = MIDI_PORT_STATE_OPEN_IDLE;
315         // check to see if the port is idle, then set to active
316         if (!mPort->state.compare_exchange_strong(portState, MIDI_PORT_STATE_OPEN_ACTIVE)) {
317             // The port not idle or has been closed.
318             return AMEDIA_ERROR_UNKNOWN;
319         }
320 
321         struct pollfd checkFds[1] = { { mPort->ufd, POLLIN, 0 } };
322         if (poll(checkFds, 1, 0) < 1) {
323             // Nothing there
324             return 0;
325         }
326 
327         uint8_t readBuffer[AMIDI_PACKET_SIZE];
328         ssize_t readCount = read(mPort->ufd, readBuffer, sizeof(readBuffer));
329         if (readCount == EINTR || readCount < 1) {
330             return  AMEDIA_ERROR_UNKNOWN;
331         }
332 
333         // see Packet Format definition at the top of this file.
334         size_t numMessageBytes = 0;
335         *opcodePtr = readBuffer[0];
336         if (*opcodePtr == AMIDI_OPCODE_DATA && readCount >= AMIDI_PACKET_OVERHEAD) {
337             numMessageBytes = readCount - AMIDI_PACKET_OVERHEAD;
338             numMessageBytes = std::min(maxBytes, numMessageBytes);
339             memcpy(buffer, readBuffer + 1, numMessageBytes);
340             if (timestampPtr != nullptr) {
341                 memcpy(timestampPtr, readBuffer + readCount - sizeof(uint64_t),
342                         sizeof(*timestampPtr));
343             }
344         }
345         *numBytesReceivedPtr = numMessageBytes;
346         return 1;
347     }
348 
349 private:
350     AMIDI_Port *mPort;
351 };
352 
AMidiOutputPort_receive(const AMidiOutputPort * outputPort,int32_t * opcodePtr,uint8_t * buffer,size_t maxBytes,size_t * numBytesReceivedPtr,int64_t * timestampPtr)353 ssize_t AMIDI_API AMidiOutputPort_receive(const AMidiOutputPort *outputPort, int32_t *opcodePtr,
354          uint8_t *buffer, size_t maxBytes, size_t* numBytesReceivedPtr, int64_t *timestampPtr) {
355 
356     if (outputPort == nullptr || buffer == nullptr) {
357         return -EINVAL;
358     }
359 
360    return MidiReceiver((AMIDI_Port*)outputPort).receive(opcodePtr, buffer, maxBytes,
361            numBytesReceivedPtr, timestampPtr);
362 }
363 
AMidiOutputPort_close(const AMidiOutputPort * outputPort)364 void AMIDI_API AMidiOutputPort_close(const AMidiOutputPort *outputPort) {
365     AMIDI_closePort((AMIDI_Port*)outputPort);
366 }
367 
368 /*
369  * Input (sending) API
370  */
AMidiInputPort_open(const AMidiDevice * device,int32_t portNumber,AMidiInputPort ** outInputPortPtr)371 media_status_t AMIDI_API AMidiInputPort_open(const AMidiDevice *device, int32_t portNumber,
372         AMidiInputPort **outInputPortPtr) {
373     return AMIDI_openPort(device, portNumber, PORTTYPE_INPUT, (AMIDI_Port**)outInputPortPtr);
374 }
375 
AMidiInputPort_close(const AMidiInputPort * inputPort)376 void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort) {
377     AMIDI_closePort((AMIDI_Port*)inputPort);
378 }
379 
AMIDI_makeSendBuffer(uint8_t * buffer,const uint8_t * data,size_t numBytes,uint64_t timestamp)380 static ssize_t AMIDI_makeSendBuffer(
381         uint8_t *buffer, const uint8_t *data, size_t numBytes, uint64_t timestamp) {
382     // Error checking will happen in the caller since this isn't an API function.
383     buffer[0] = AMIDI_OPCODE_DATA;
384     memcpy(buffer + 1, data, numBytes);
385     memcpy(buffer + 1 + numBytes, &timestamp, sizeof(timestamp));
386     return numBytes + AMIDI_PACKET_OVERHEAD;
387 }
388 
AMidiInputPort_send(const AMidiInputPort * inputPort,const uint8_t * buffer,size_t numBytes)389 ssize_t AMIDI_API AMidiInputPort_send(const AMidiInputPort *inputPort, const uint8_t *buffer,
390                             size_t numBytes) {
391     return AMidiInputPort_sendWithTimestamp(inputPort, buffer, numBytes, 0);
392 }
393 
AMidiInputPort_sendWithTimestamp(const AMidiInputPort * inputPort,const uint8_t * data,size_t numBytes,int64_t timestamp)394 ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort,
395         const uint8_t *data, size_t numBytes, int64_t timestamp) {
396     if (inputPort == nullptr || data == nullptr) {
397         return AMEDIA_ERROR_INVALID_PARAMETER;
398     }
399 
400     // AMIDI_logBuffer(data, numBytes);
401 
402     uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD];
403     size_t numSent = 0;
404     while (numSent < numBytes) {
405         size_t blockSize = AMIDI_BUFFER_SIZE;
406         blockSize = std::min(blockSize, numBytes - numSent);
407 
408         ssize_t numTransferBytes =
409                 AMIDI_makeSendBuffer(writeBuffer, data + numSent, blockSize, timestamp);
410         ssize_t numWritten = write(((AMIDI_Port*)inputPort)->ufd, writeBuffer, numTransferBytes);
411         if (numWritten < 0) {
412             break;  // error so bail out.
413         }
414         if (numWritten < numTransferBytes) {
415             ALOGE("AMidiInputPort_sendWithTimestamp Couldn't write MIDI data buffer."
416                   " requested:%zu, written%zu",numTransferBytes, numWritten);
417             break;  // bail
418         }
419 
420         numSent += numWritten  - AMIDI_PACKET_OVERHEAD;
421     }
422 
423     return numSent;
424 }
425 
AMidiInputPort_sendFlush(const AMidiInputPort * inputPort)426 media_status_t AMIDI_API AMidiInputPort_sendFlush(const AMidiInputPort *inputPort) {
427     if (inputPort == nullptr) {
428         return AMEDIA_ERROR_INVALID_PARAMETER;
429     }
430 
431     uint8_t opCode = AMIDI_OPCODE_FLUSH;
432     ssize_t numTransferBytes = 1;
433     ssize_t numWritten = write(((AMIDI_Port*)inputPort)->ufd, &opCode, numTransferBytes);
434 
435     if (numWritten < numTransferBytes) {
436         ALOGE("AMidiInputPort_flush Couldn't write MIDI flush. requested:%zd, written:%zd",
437                 numTransferBytes, numWritten);
438         return AMEDIA_ERROR_UNSUPPORTED;
439     }
440 
441     return AMEDIA_OK;
442 }
443 
444