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("%s server exception code: %d", __func__, txResult.exceptionCode());
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     outDeviceInfoPtr->defaultProtocol = deviceInfo.getDefaultProtocol();
142 
143     return AMEDIA_OK;
144 }
145 
AMidiDevice_fromJava(JNIEnv * env,jobject j_midiDeviceObj,AMidiDevice ** devicePtrPtr)146 media_status_t AMIDI_API AMidiDevice_fromJava(JNIEnv *env, jobject j_midiDeviceObj,
147         AMidiDevice** devicePtrPtr)
148 {
149     if (j_midiDeviceObj == nullptr) {
150         ALOGE("AMidiDevice_fromJava() invalid MidiDevice object.");
151         return AMEDIA_ERROR_INVALID_OBJECT;
152     }
153 
154     {
155         std::lock_guard<std::mutex> guard(openMutex);
156 
157         long handle = env->GetLongField(j_midiDeviceObj, gFidMidiNativeHandle);
158         if (handle != 0) {
159             // Already opened by someone.
160             return AMEDIA_ERROR_INVALID_OBJECT;
161         }
162 
163         jobject serverBinderObj = env->GetObjectField(j_midiDeviceObj, gFidMidiDeviceServerBinder);
164         sp<IBinder> serverBinder = android::ibinderForJavaObject(env, serverBinderObj);
165         if (serverBinder.get() == nullptr) {
166             ALOGE("AMidiDevice_fromJava couldn't connect to native MIDI server.");
167             return AMEDIA_ERROR_UNKNOWN;
168         }
169 
170         // don't check allocation failures, just abort..
171         AMidiDevice* devicePtr = new AMidiDevice;
172         devicePtr->server = new BpMidiDeviceServer(serverBinder);
173         jobject midiDeviceInfoObj = env->GetObjectField(j_midiDeviceObj, gFidMidiDeviceInfo);
174         devicePtr->deviceId = env->GetIntField(midiDeviceInfoObj, mFidMidiDeviceId);
175 
176         // Synchronize with the associated Java MidiDevice.
177         env->SetLongField(j_midiDeviceObj, gFidMidiNativeHandle, (long)devicePtr);
178         env->GetJavaVM(&devicePtr->javaVM);
179         devicePtr->midiDeviceObj = env->NewGlobalRef(j_midiDeviceObj);
180 
181         if (AMIDI_getDeviceInfo(devicePtr, &devicePtr->deviceInfo) != AMEDIA_OK) {
182             // This is weird, but maybe not fatal?
183             ALOGE("AMidiDevice_fromJava couldn't retrieve attributes of native device.");
184         }
185 
186         *devicePtrPtr = devicePtr;
187     }
188 
189     return AMEDIA_OK;
190 }
191 
AMidiDevice_release(const AMidiDevice * device)192 media_status_t AMIDI_API AMidiDevice_release(const AMidiDevice *device)
193 {
194     if (device == nullptr || device->midiDeviceObj == nullptr) {
195         return AMEDIA_ERROR_INVALID_PARAMETER;
196     }
197 
198     JNIEnv* env;
199     jint err = device->javaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
200     LOG_ALWAYS_FATAL_IF(err != JNI_OK, "AMidiDevice_release Error accessing JNIEnv err:%d", err);
201 
202     // Synchronize with the associated Java MidiDevice.
203     {
204         std::lock_guard<std::mutex> guard(openMutex);
205         long handle = env->GetLongField(device->midiDeviceObj, gFidMidiNativeHandle);
206         if (handle == 0) {
207             // Not opened as native.
208             ALOGE("AMidiDevice_release() device not opened in native client.");
209             return AMEDIA_ERROR_INVALID_OBJECT;
210         }
211 
212         env->SetLongField(device->midiDeviceObj, gFidMidiNativeHandle, 0L);
213     }
214     env->DeleteGlobalRef(device->midiDeviceObj);
215 
216     delete device;
217 
218     return AMEDIA_OK;
219 }
220 
AMidiDevice_getType(const AMidiDevice * device)221 int32_t AMIDI_API AMidiDevice_getType(const AMidiDevice *device) {
222     if (device == nullptr) {
223         return AMEDIA_ERROR_INVALID_PARAMETER;
224     }
225     return device->deviceInfo.type;
226 }
227 
AMidiDevice_getNumInputPorts(const AMidiDevice * device)228 ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) {
229     if (device == nullptr) {
230         return AMEDIA_ERROR_INVALID_PARAMETER;
231     }
232     return device->deviceInfo.inputPortCount;
233 }
234 
AMidiDevice_getNumOutputPorts(const AMidiDevice * device)235 ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) {
236     if (device == nullptr) {
237         return AMEDIA_ERROR_INVALID_PARAMETER;
238     }
239     return device->deviceInfo.outputPortCount;
240 }
241 
AMidiDevice_getDefaultProtocol(const AMidiDevice * device)242 AMidiDevice_Protocol AMIDI_API AMidiDevice_getDefaultProtocol(const AMidiDevice *device) {
243     if (device == nullptr) {
244         return AMIDI_DEVICE_PROTOCOL_UNKNOWN;
245     }
246     return static_cast<AMidiDevice_Protocol>(device->deviceInfo.defaultProtocol);
247 }
248 
249 /*
250  * Port Helpers
251  */
AMIDI_openPort(const AMidiDevice * device,int32_t portNumber,int type,AMIDI_Port ** portPtr)252 static media_status_t AMIDI_openPort(const AMidiDevice *device, int32_t portNumber, int type,
253         AMIDI_Port **portPtr) {
254     if (device == nullptr) {
255         return AMEDIA_ERROR_INVALID_PARAMETER;
256     }
257 
258     sp<BBinder> portToken(new BBinder());
259     unique_fd ufd;
260     Status txResult = type == PORTTYPE_OUTPUT
261             ? device->server->openOutputPort(portToken, portNumber, &ufd)
262             : device->server->openInputPort(portToken, portNumber, &ufd);
263     if (!txResult.isOk()) {
264         ALOGE("%s server exception code: %d", __func__, txResult.exceptionCode());
265         return AMEDIA_ERROR_UNKNOWN;
266     }
267 
268     AMIDI_Port *port = new AMIDI_Port;
269     port->state = MIDI_PORT_STATE_OPEN_IDLE;
270     port->device = device;
271     port->binderToken = portToken;
272     port->ufd = std::move(ufd);
273 
274     *portPtr = port;
275 
276     return AMEDIA_OK;
277 }
278 
AMIDI_closePort(AMIDI_Port * port)279 static void AMIDI_closePort(AMIDI_Port *port) {
280     if (port == nullptr) {
281         return;
282     }
283 
284     int portState = MIDI_PORT_STATE_OPEN_IDLE;
285     while (!port->state.compare_exchange_weak(portState, MIDI_PORT_STATE_CLOSED)) {
286         if (portState == MIDI_PORT_STATE_CLOSED) {
287             return; // Already closed
288         }
289     }
290 
291     Status txResult = port->device->server->closePort(port->binderToken);
292     if (!txResult.isOk()) {
293         ALOGE("%s server exception code: %d", __func__, txResult.exceptionCode());
294     }
295 
296     delete port;
297 }
298 
299 /*
300  * Output (receiving) API
301  */
AMidiOutputPort_open(const AMidiDevice * device,int32_t portNumber,AMidiOutputPort ** outOutputPortPtr)302 media_status_t AMIDI_API AMidiOutputPort_open(const AMidiDevice *device, int32_t portNumber,
303         AMidiOutputPort **outOutputPortPtr) {
304     return AMIDI_openPort(device, portNumber, PORTTYPE_OUTPUT, (AMIDI_Port**)outOutputPortPtr);
305 }
306 
307 /*
308  *  A little RAII (https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization)
309  *  class to ensure that the port state is correct irrespective of errors.
310  */
311 class MidiReceiver {
312 public:
MidiReceiver(AMIDI_Port * port)313     MidiReceiver(AMIDI_Port *port) : mPort(port) {}
314 
~MidiReceiver()315     ~MidiReceiver() {
316         // flag the port state to idle
317         mPort->state.store(MIDI_PORT_STATE_OPEN_IDLE);
318     }
319 
receive(int32_t * opcodePtr,uint8_t * buffer,size_t maxBytes,size_t * numBytesReceivedPtr,int64_t * timestampPtr)320     ssize_t receive(int32_t *opcodePtr, uint8_t *buffer, size_t maxBytes,
321             size_t *numBytesReceivedPtr, int64_t *timestampPtr) {
322         int portState = MIDI_PORT_STATE_OPEN_IDLE;
323         // check to see if the port is idle, then set to active
324         if (!mPort->state.compare_exchange_strong(portState, MIDI_PORT_STATE_OPEN_ACTIVE)) {
325             // The port not idle or has been closed.
326             return AMEDIA_ERROR_UNKNOWN;
327         }
328 
329         struct pollfd checkFds[1] = { { mPort->ufd, POLLIN, 0 } };
330         if (poll(checkFds, 1, 0) < 1) {
331             // Nothing there
332             return 0;
333         }
334 
335         uint8_t readBuffer[AMIDI_PACKET_SIZE];
336         ssize_t readCount = TEMP_FAILURE_RETRY(read(mPort->ufd, readBuffer, sizeof(readBuffer)));
337         if (readCount < 1) {
338             return  AMEDIA_ERROR_UNKNOWN;
339         }
340 
341         // see Packet Format definition at the top of this file.
342         size_t numMessageBytes = 0;
343         *opcodePtr = readBuffer[0];
344         if (*opcodePtr == AMIDI_OPCODE_DATA && readCount >= AMIDI_PACKET_OVERHEAD) {
345             numMessageBytes = readCount - AMIDI_PACKET_OVERHEAD;
346             numMessageBytes = std::min(maxBytes, numMessageBytes);
347             memcpy(buffer, readBuffer + 1, numMessageBytes);
348             if (timestampPtr != nullptr) {
349                 memcpy(timestampPtr, readBuffer + readCount - sizeof(uint64_t),
350                         sizeof(*timestampPtr));
351             }
352         }
353         *numBytesReceivedPtr = numMessageBytes;
354         return 1;
355     }
356 
357 private:
358     AMIDI_Port *mPort;
359 };
360 
AMidiOutputPort_receive(const AMidiOutputPort * outputPort,int32_t * opcodePtr,uint8_t * buffer,size_t maxBytes,size_t * numBytesReceivedPtr,int64_t * timestampPtr)361 ssize_t AMIDI_API AMidiOutputPort_receive(const AMidiOutputPort *outputPort, int32_t *opcodePtr,
362          uint8_t *buffer, size_t maxBytes, size_t* numBytesReceivedPtr, int64_t *timestampPtr) {
363 
364     if (outputPort == nullptr || buffer == nullptr) {
365         return -EINVAL;
366     }
367 
368    return MidiReceiver((AMIDI_Port*)outputPort).receive(opcodePtr, buffer, maxBytes,
369            numBytesReceivedPtr, timestampPtr);
370 }
371 
AMidiOutputPort_close(const AMidiOutputPort * outputPort)372 void AMIDI_API AMidiOutputPort_close(const AMidiOutputPort *outputPort) {
373     AMIDI_closePort((AMIDI_Port*)outputPort);
374 }
375 
376 /*
377  * Input (sending) API
378  */
AMidiInputPort_open(const AMidiDevice * device,int32_t portNumber,AMidiInputPort ** outInputPortPtr)379 media_status_t AMIDI_API AMidiInputPort_open(const AMidiDevice *device, int32_t portNumber,
380         AMidiInputPort **outInputPortPtr) {
381     return AMIDI_openPort(device, portNumber, PORTTYPE_INPUT, (AMIDI_Port**)outInputPortPtr);
382 }
383 
AMidiInputPort_close(const AMidiInputPort * inputPort)384 void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort) {
385     AMIDI_closePort((AMIDI_Port*)inputPort);
386 }
387 
AMIDI_makeSendBuffer(uint8_t * buffer,const uint8_t * data,size_t numBytes,uint64_t timestamp)388 static ssize_t AMIDI_makeSendBuffer(
389         uint8_t *buffer, const uint8_t *data, size_t numBytes, uint64_t timestamp) {
390     // Error checking will happen in the caller since this isn't an API function.
391     buffer[0] = AMIDI_OPCODE_DATA;
392     memcpy(buffer + 1, data, numBytes);
393     memcpy(buffer + 1 + numBytes, &timestamp, sizeof(timestamp));
394     return numBytes + AMIDI_PACKET_OVERHEAD;
395 }
396 
AMidiInputPort_send(const AMidiInputPort * inputPort,const uint8_t * buffer,size_t numBytes)397 ssize_t AMIDI_API AMidiInputPort_send(const AMidiInputPort *inputPort, const uint8_t *buffer,
398                             size_t numBytes) {
399     return AMidiInputPort_sendWithTimestamp(inputPort, buffer, numBytes, 0);
400 }
401 
AMidiInputPort_sendWithTimestamp(const AMidiInputPort * inputPort,const uint8_t * data,size_t numBytes,int64_t timestamp)402 ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort,
403         const uint8_t *data, size_t numBytes, int64_t timestamp) {
404     if (inputPort == nullptr || data == nullptr || numBytes < 0 || timestamp < 0) {
405         return AMEDIA_ERROR_INVALID_PARAMETER;
406     }
407 
408     if (numBytes == 0) {
409         return 0;
410     }
411 
412     // AMIDI_logBuffer(data, numBytes);
413 
414     uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD];
415     size_t numSent = 0;
416     while (numSent < numBytes) {
417         size_t blockSize = AMIDI_BUFFER_SIZE;
418         blockSize = std::min(blockSize, numBytes - numSent);
419 
420         ssize_t numTransferBytes =
421                 AMIDI_makeSendBuffer(writeBuffer, data + numSent, blockSize, timestamp);
422         ssize_t numWritten = TEMP_FAILURE_RETRY(write(((AMIDI_Port*)inputPort)->ufd, writeBuffer,
423                                                       numTransferBytes));
424         if (numWritten < 0) {
425             break;  // error so bail out.
426         }
427         if (numWritten < numTransferBytes) {
428             ALOGE("AMidiInputPort_sendWithTimestamp Couldn't write MIDI data buffer."
429                   " requested:%zu, written%zu",numTransferBytes, numWritten);
430             break;  // bail
431         }
432 
433         numSent += numWritten  - AMIDI_PACKET_OVERHEAD;
434     }
435 
436     return numSent;
437 }
438 
AMidiInputPort_sendFlush(const AMidiInputPort * inputPort)439 media_status_t AMIDI_API AMidiInputPort_sendFlush(const AMidiInputPort *inputPort) {
440     if (inputPort == nullptr) {
441         return AMEDIA_ERROR_INVALID_PARAMETER;
442     }
443 
444     uint8_t opCode = AMIDI_OPCODE_FLUSH;
445     ssize_t numTransferBytes = 1;
446     ssize_t numWritten = TEMP_FAILURE_RETRY(write(((AMIDI_Port*)inputPort)->ufd, &opCode,
447                                                   numTransferBytes));
448 
449     if (numWritten < numTransferBytes) {
450         ALOGE("AMidiInputPort_flush Couldn't write MIDI flush. requested:%zd, written:%zd",
451                 numTransferBytes, numWritten);
452         return AMEDIA_ERROR_UNSUPPORTED;
453     }
454 
455     return AMEDIA_OK;
456 }
457 
458