nativemidi: Prototype demonstrating native access to IMidiDeviceServer

Framework changes and a demo app
Comment and finalized Native MIDI API
Replaced fixed PortRegistry tables with std::map.
more error handling.
Removed not-very-useful MidiDeviceManager class.
Made Java API functions @hide.

Bug: 30252756

Test: Manual
Change-Id: Iae98e589f38ef6d625ff0842401193fe98c5d881
diff --git a/media/native/midi/midi.cpp b/media/native/midi/midi.cpp
new file mode 100644
index 0000000..1bf0bd0
--- /dev/null
+++ b/media/native/midi/midi.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2017 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 "NativeMIDI"
+
+#include <poll.h>
+#include <unistd.h>
+
+#include <binder/Binder.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+
+#include "android/media/midi/BpMidiDeviceServer.h"
+#include "media/MidiDeviceInfo.h"
+#include "MidiDeviceRegistry.h"
+#include "MidiPortRegistry.h"
+
+#include "midi.h"
+
+using android::IBinder;
+using android::BBinder;
+using android::OK;
+using android::sp;
+using android::status_t;
+using android::base::unique_fd;
+using android::binder::Status;
+using android::media::midi::BpMidiDeviceServer;
+using android::media::midi::MidiDeviceInfo;
+using android::media::midi::MidiDeviceRegistry;
+using android::media::midi::MidiPortRegistry;
+
+#define SIZE_MIDIRECEIVEBUFFER AMIDI_BUFFER_SIZE
+
+/* TRANSFER PACKET FORMAT (as defined in MidiPortImpl.java)
+ *
+ * Transfer packet format is as follows (see MidiOutputPort.mThread.run() to see decomposition):
+ * |oc|md|md| ......... |md|ts|ts|ts|ts|ts|ts|ts|ts|
+ *  ^ +--------------------+-----------------------+
+ *  |  ^                    ^
+ *  |  |                    |
+ *  |  |                    + timestamp (8 bytes)
+ *  |  |
+ *  |  + MIDI data bytes (numBytes bytes)
+ *  |
+ *  + OpCode (AMIDI_OPCODE_DATA)
+ *
+ *  NOTE: The socket pair is configured to use SOCK_SEQPACKET mode.
+ *  SOCK_SEQPACKET, for a sequenced-packet socket that is connection-oriented, preserves message
+ *  boundaries, and delivers messages in the order that they were sent.
+ *  So 'read()' always returns a whole message.
+ */
+
+status_t AMIDI_getDeviceById(int32_t id, AMIDI_Device *devicePtr) {
+    return MidiDeviceRegistry::getInstance().obtainDeviceToken(id, devicePtr);
+}
+
+status_t AMIDI_getDeviceInfo(AMIDI_Device device, AMIDI_DeviceInfo *deviceInfoPtr) {
+    sp<BpMidiDeviceServer> deviceServer;
+    status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+    if (result != OK) {
+        ALOGE("AMIDI_getDeviceInfo bad device token %d: %d", device, result);
+        return result;
+    }
+
+    MidiDeviceInfo deviceInfo;
+    Status txResult = deviceServer->getDeviceInfo(&deviceInfo);
+    if (!txResult.isOk()) {
+        ALOGE("AMIDI_getDeviceInfo transaction error: %d", txResult.transactionError());
+        return txResult.transactionError();
+    }
+
+    deviceInfoPtr->type = deviceInfo.getType();
+    deviceInfoPtr->uid = deviceInfo.getUid();
+    deviceInfoPtr->isPrivate = deviceInfo.isPrivate();
+    deviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size();
+    deviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size();
+    return OK;
+}
+
+/*
+ * Output (receiving) API
+ */
+status_t AMIDI_openOutputPort(AMIDI_Device device, int portNumber, AMIDI_OutputPort *outputPortPtr) {
+    sp<BpMidiDeviceServer> deviceServer;
+    status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+    if (result != OK) {
+        ALOGE("AMIDI_openOutputPort bad device token %d: %d", device, result);
+        return result;
+    }
+
+    sp<BBinder> portToken(new BBinder());
+    unique_fd ufd;
+    Status txResult = deviceServer->openOutputPort(portToken, portNumber, &ufd);
+    if (!txResult.isOk()) {
+        ALOGE("AMIDI_openOutputPort transaction error: %d", txResult.transactionError());
+        return txResult.transactionError();
+    }
+
+    result = MidiPortRegistry::getInstance().addOutputPort(
+            device, portToken, std::move(ufd), outputPortPtr);
+    if (result != OK) {
+        ALOGE("AMIDI_openOutputPort port registration error: %d", result);
+        // Close port
+        return result;
+    }
+    return OK;
+}
+
+ssize_t AMIDI_receive(AMIDI_OutputPort outputPort, AMIDI_Message *messages, ssize_t maxMessages) {
+    unique_fd *ufd;
+    // TODO: May return a nicer self-unlocking object
+    status_t result = MidiPortRegistry::getInstance().getOutputPortFdAndLock(outputPort, &ufd);
+    if (result != OK) {
+        return result;
+    }
+
+    ssize_t messagesRead = 0;
+    while (messagesRead < maxMessages) {
+        struct pollfd checkFds[1] = { { *ufd, POLLIN, 0 } };
+        int pollResult = poll(checkFds, 1, 0);
+        if (pollResult < 1) {
+            result = android::INVALID_OPERATION;
+            break;
+        }
+
+        AMIDI_Message *message = &messages[messagesRead];
+        uint8_t readBuffer[AMIDI_PACKET_SIZE];
+        memset(readBuffer, 0, sizeof(readBuffer));
+        ssize_t readCount = read(*ufd, readBuffer, sizeof(readBuffer));
+        if (readCount == EINTR) {
+            continue;
+        }
+        if (readCount < 1) {
+            result = android::NOT_ENOUGH_DATA;
+            break;
+        }
+
+        // set Packet Format definition at the top of this file.
+        size_t dataSize = 0;
+        message->opcode = readBuffer[0];
+        message->timestamp = 0;
+        if (message->opcode == AMIDI_OPCODE_DATA && readCount >= AMIDI_PACKET_OVERHEAD) {
+            dataSize = readCount - AMIDI_PACKET_OVERHEAD;
+            if (dataSize) {
+                memcpy(message->buffer, readBuffer + 1, dataSize);
+            }
+            message->timestamp = *(uint64_t*) (readBuffer + readCount - sizeof(uint64_t));
+        }
+        message->len = dataSize;
+        ++messagesRead;
+    }
+
+    MidiPortRegistry::getInstance().unlockOutputPort(outputPort);
+    return result == OK ? messagesRead : result;
+}
+
+status_t AMIDI_closeOutputPort(AMIDI_OutputPort outputPort) {
+    AMIDI_Device device;
+    sp<IBinder> portToken;
+    status_t result =
+        MidiPortRegistry::getInstance().removeOutputPort(outputPort, &device, &portToken);
+    if (result != OK) {
+        return result;
+    }
+
+    sp<BpMidiDeviceServer> deviceServer;
+    result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+    if (result != OK) {
+        return result;
+    }
+
+    Status txResult = deviceServer->closePort(portToken);
+    if (!txResult.isOk()) {
+        return txResult.transactionError();
+    }
+    return OK;
+}
+
+/*
+ * Input (sending) API
+ */
+status_t AMIDI_openInputPort(AMIDI_Device device, int portNumber, AMIDI_InputPort *inputPortPtr) {
+    sp<BpMidiDeviceServer> deviceServer;
+    status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+    if (result != OK) {
+        ALOGE("AMIDI_openInputPort bad device token %d: %d", device, result);
+        return result;
+    }
+
+    sp<BBinder> portToken(new BBinder());
+    unique_fd ufd; // this is the file descriptor of the "receive" port s
+    Status txResult = deviceServer->openInputPort(portToken, portNumber, &ufd);
+    if (!txResult.isOk()) {
+        ALOGE("AMIDI_openInputPort transaction error: %d", txResult.transactionError());
+        return txResult.transactionError();
+    }
+
+    result = MidiPortRegistry::getInstance().addInputPort(
+            device, portToken, std::move(ufd), inputPortPtr);
+    if (result != OK) {
+        ALOGE("AMIDI_openInputPort port registration error: %d", result);
+        // Close port
+        return result;
+    }
+
+    return OK;
+}
+
+status_t AMIDI_closeInputPort(AMIDI_InputPort inputPort) {
+    AMIDI_Device device;
+    sp<IBinder> portToken;
+    status_t result = MidiPortRegistry::getInstance().removeInputPort(
+            inputPort, &device, &portToken);
+    if (result != OK) {
+        ALOGE("AMIDI_closeInputPort remove port error: %d", result);
+        return result;
+    }
+
+    sp<BpMidiDeviceServer> deviceServer;
+    result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+    if (result != OK) {
+        ALOGE("AMIDI_closeInputPort can't find device error: %d", result);
+        return result;
+    }
+
+    Status txResult = deviceServer->closePort(portToken);
+    if (!txResult.isOk()) {
+        result = txResult.transactionError();
+        ALOGE("AMIDI_closeInputPort transaction error: %d", result);
+        return result;
+    }
+
+    return OK;
+}
+
+ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort /*inputPort*/) {
+    return SIZE_MIDIRECEIVEBUFFER;
+}
+
+static ssize_t AMIDI_makeSendBuffer(uint8_t *buffer, uint8_t *data, ssize_t numBytes, uint64_t timestamp) {
+    buffer[0] = AMIDI_OPCODE_DATA;
+    memcpy(buffer + 1, data, numBytes);
+    memcpy(buffer + 1 + numBytes, &timestamp, sizeof(timestamp));
+    return numBytes + AMIDI_PACKET_OVERHEAD;
+}
+
+// Handy debugging function.
+//static void AMIDI_logBuffer(uint8_t *data, size_t numBytes) {
+//    for (size_t index = 0; index < numBytes; index++) {
+//      ALOGI("  data @%zu [0x%X]", index, data[index]);
+//    }
+//}
+
+ssize_t AMIDI_send(AMIDI_InputPort inputPort, uint8_t *buffer, ssize_t numBytes) {
+    return AMIDI_sendWithTimestamp(inputPort, buffer, numBytes, 0);
+}
+
+ssize_t AMIDI_sendWithTimestamp(AMIDI_InputPort inputPort, uint8_t *data,
+        ssize_t numBytes, int64_t timestamp) {
+
+    if (numBytes > SIZE_MIDIRECEIVEBUFFER) {
+        return android::BAD_VALUE;
+    }
+
+    // AMIDI_logBuffer(data, numBytes);
+
+    unique_fd *ufd = NULL;
+    status_t result = MidiPortRegistry::getInstance().getInputPortFd(inputPort, &ufd);
+    if (result != OK) {
+        return result;
+    }
+
+    uint8_t writeBuffer[SIZE_MIDIRECEIVEBUFFER + AMIDI_PACKET_OVERHEAD];
+    ssize_t numTransferBytes = AMIDI_makeSendBuffer(writeBuffer, data, numBytes, timestamp);
+    ssize_t numWritten = write(*ufd, writeBuffer, numTransferBytes);
+
+    if (numWritten < numTransferBytes) {
+        ALOGE("AMIDI_sendWithTimestamp Couldn't write MIDI data buffer. requested:%zu, written%zu",
+                numTransferBytes, numWritten);
+    }
+
+    return numWritten - AMIDI_PACKET_OVERHEAD;
+}
+
+status_t AMIDI_flush(AMIDI_InputPort inputPort) {
+    unique_fd *ufd = NULL;
+    status_t result = MidiPortRegistry::getInstance().getInputPortFd(inputPort, &ufd);
+    if (result != OK) {
+        return result;
+    }
+
+    uint8_t opCode = AMIDI_OPCODE_FLUSH;
+    ssize_t numTransferBytes = 1;
+    ssize_t numWritten = write(*ufd, &opCode, numTransferBytes);
+
+    if (numWritten < numTransferBytes) {
+        ALOGE("AMIDI_flush Couldn't write MIDI flush. requested:%zu, written%zu",
+                numTransferBytes, numWritten);
+        return android::INVALID_OPERATION;
+    }
+
+    return OK;
+}
+