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/Android.bp b/media/native/midi/Android.bp
new file mode 100644
index 0000000..3500805
--- /dev/null
+++ b/media/native/midi/Android.bp
@@ -0,0 +1,21 @@
+// 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.
+
+// The headers module is in frameworks/media/native/midi/Android.bp.
+ndk_library {
+    name: "libmidi.ndk",
+    symbol_file: "libmidi.map.txt",
+    first_version: "26",
+//    unversioned_until: "current",
+}
diff --git a/media/native/midi/Android.mk b/media/native/midi/Android.mk
new file mode 100644
index 0000000..b91c430
--- /dev/null
+++ b/media/native/midi/Android.mk
@@ -0,0 +1,22 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	../../java/android/media/midi/IMidiDeviceServer.aidl \
+	midi.cpp \
+	MidiDeviceRegistry.cpp \
+	MidiPortRegistry.cpp
+
+LOCAL_AIDL_INCLUDES := \
+	$(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \
+	frameworks/native/aidl/binder
+
+LOCAL_CFLAGS += -Wall -Werror -O0
+
+LOCAL_MODULE := libmidi
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := liblog libbinder libutils libmedia
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/native/midi/MidiDeviceRegistry.cpp b/media/native/midi/MidiDeviceRegistry.cpp
new file mode 100644
index 0000000..8854a08
--- /dev/null
+++ b/media/native/midi/MidiDeviceRegistry.cpp
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+#include "MidiDeviceRegistry.h"
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(media::midi::MidiDeviceRegistry);
+
+namespace media {
+namespace midi {
+
+MidiDeviceRegistry::MidiDeviceRegistry() : mNextDeviceToken(1) {
+}
+
+status_t MidiDeviceRegistry::addDevice(sp<BpMidiDeviceServer> server, int32_t deviceId) {
+    if (server.get() == nullptr) {
+        return -EINVAL;
+    }
+
+    std::lock_guard<std::mutex> guard(mMapsLock);
+    mServers[deviceId] = server;
+    return OK;
+}
+
+status_t MidiDeviceRegistry::removeDevice(int32_t deviceId) {
+    std::lock_guard<std::mutex> guard(mMapsLock);
+    mServers.erase(deviceId);
+    const auto& iter = mUidToToken.find(deviceId);
+    if (iter != mUidToToken.end()) {
+        mTokenToUid.erase(iter->second);
+        mUidToToken.erase(iter);
+    }
+    return OK;
+}
+
+//NOTE: This creates an entry if not found, or returns an existing one.
+status_t MidiDeviceRegistry::obtainDeviceToken(int32_t deviceId, AMIDI_Device *deviceTokenPtr) {
+    std::lock_guard<std::mutex> guard(mMapsLock);
+    const auto& serversIter = mServers.find(deviceId);
+    if (serversIter == mServers.end()) {
+        // Not found.
+        return -EINVAL;
+    }
+
+    const auto& iter = mUidToToken.find(deviceId);
+    if (iter != mUidToToken.end()) {
+        *deviceTokenPtr = iter->second;
+    } else {
+        *deviceTokenPtr = mNextDeviceToken++;
+        mTokenToUid[*deviceTokenPtr] = deviceId;
+        mUidToToken[deviceId] = *deviceTokenPtr;
+    }
+    return OK;
+}
+
+status_t MidiDeviceRegistry::releaseDevice(AMIDI_Device deviceToken) {
+    std::lock_guard<std::mutex> guard(mMapsLock);
+    const auto& iter = mTokenToUid.find(deviceToken);
+    if (iter == mTokenToUid.end()) {
+        // Not found
+        return -EINVAL;
+    }
+
+    mServers.erase(iter->second);
+    mUidToToken.erase(iter->second);
+    mTokenToUid.erase(iter);
+    return OK;
+}
+
+status_t MidiDeviceRegistry::getDeviceByToken(
+        AMIDI_Device deviceToken, sp<BpMidiDeviceServer> *devicePtr) {
+    std::lock_guard<std::mutex> guard(mMapsLock);
+    int32_t id = -1;
+    {
+        const auto& iter = mTokenToUid.find(deviceToken);
+        if (iter == mTokenToUid.end()) {
+            return -EINVAL;
+        }
+        id = iter->second;
+    }
+    const auto& iter = mServers.find(id);
+    if (iter == mServers.end()) {
+        return -EINVAL;
+    }
+
+    *devicePtr = iter->second;
+    return OK;
+}
+
+} // namespace midi
+} // namespace media
+} // namespace android
diff --git a/media/native/midi/MidiDeviceRegistry.h b/media/native/midi/MidiDeviceRegistry.h
new file mode 100644
index 0000000..93be733
--- /dev/null
+++ b/media/native/midi/MidiDeviceRegistry.h
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
+#define ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
+
+#include <map>
+#include <mutex>
+
+#include <binder/IBinder.h>
+#include <utils/Errors.h>
+#include <utils/Singleton.h>
+
+#include "android/media/midi/BpMidiDeviceServer.h"
+#include "midi.h"
+
+namespace android {
+namespace media {
+namespace midi {
+
+/*
+ * Maintains a thread-safe, (singleton) list of MIDI devices with associated Binder interfaces,
+ * which are exposed to the Native API via (Java) MidiDevice.mirrorToNative() &
+ * MidiDevice.removeFromNative().
+ * (Called via MidiDeviceManager::addDevice() MidiManager::removeDevice()).
+ */
+class MidiDeviceRegistry : public Singleton<MidiDeviceRegistry> {
+  public:
+    /* Add a MIDI Device to the registry.
+     *
+     * server       The Binder interface to the MIDI device server.
+     * deviceUId    The unique ID of the device obtained from
+     *              the Java API via MidiDeviceInfo.getId().
+     */
+    status_t addDevice(sp<BpMidiDeviceServer> server, int32_t deviceId);
+
+    /* Remove the device (and associated server) from the Device registry.
+     *
+     * deviceUid    The ID of the device which was used in the call to addDevice().
+     */
+    status_t removeDevice(int32_t deviceId);
+
+    /* Gets a device token associated with the device ID. This is used by the
+     * native API to identify/access the device.
+     * Multiple calls without releasing the token will return the same value.
+     *
+     * deviceUid    The ID of the device.
+     * deviceTokenPtr Receives the device (native) token associated with the device ID.
+     * returns: OK on success, error code otherwise.
+     */
+    status_t obtainDeviceToken(int32_t deviceId, AMIDI_Device *deviceTokenPtr);
+
+    /*
+     * Releases the native API device token associated with a MIDI device.
+     *
+     * deviceToken The device (native) token associated with the device ID.
+     */
+    status_t releaseDevice(AMIDI_Device deviceToken);
+
+    /*
+     * Gets the Device server binder interface associated with the device token.
+     *
+     * deviceToken The device (native) token associated with the device ID.
+     */
+    status_t getDeviceByToken(AMIDI_Device deviceToken, sp<BpMidiDeviceServer> *devicePtr);
+
+  private:
+    friend class Singleton<MidiDeviceRegistry>;
+    MidiDeviceRegistry();
+
+    // Access Mutex
+    std::mutex                              mMapsLock;
+
+    // maps device IDs to servers
+    std::map<int32_t, sp<BpMidiDeviceServer>>   mServers;
+
+    // maps device tokens to device ID
+    std::map<AMIDI_Device, int32_t>         mTokenToUid;
+
+    // maps device IDs to device tokens
+    std::map<int32_t, AMIDI_Device>         mUidToToken;
+
+    // Value of next device token to dole out.
+    AMIDI_Device                            mNextDeviceToken;
+};
+
+} // namespace midi
+} // namespace media
+} // namespace android
+
+#endif // ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
diff --git a/media/native/midi/MidiPortRegistry.cpp b/media/native/midi/MidiPortRegistry.cpp
new file mode 100644
index 0000000..fa70af8
--- /dev/null
+++ b/media/native/midi/MidiPortRegistry.cpp
@@ -0,0 +1,200 @@
+/*
+ * 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.
+ */
+
+#include "MidiPortRegistry.h"
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(media::midi::MidiPortRegistry);
+
+namespace media {
+namespace midi {
+
+//TODO Note that these 2 are identical
+struct MidiPortRegistry::OutputPort {
+    AMIDI_Device device;
+    sp<IBinder> binderToken;
+    base::unique_fd ufd;
+};
+
+struct MidiPortRegistry::InputPort {
+    AMIDI_Device device;
+    sp<IBinder> binderToken;
+    base::unique_fd ufd;
+};
+
+MidiPortRegistry::MidiPortRegistry() : mNextOutputPortToken(0), mNextInputPortToken(0) {
+}
+
+status_t MidiPortRegistry::addOutputPort(
+        AMIDI_Device device,
+        sp<IBinder> portToken,
+        base::unique_fd &&ufd,
+        AMIDI_OutputPort *portPtr) {
+    *portPtr = mNextOutputPortToken++;
+
+    OutputPortEntry* portEntry = new OutputPortEntry;
+    portEntry->port = new OutputPort;
+    portEntry->state = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+    portEntry->port = new OutputPort;
+    portEntry->port->device = device;
+    portEntry->port->binderToken = portToken;
+    portEntry->port->ufd = std::move(ufd);
+
+    mOutputPortMap[*portPtr] = portEntry;
+
+    return OK;
+}
+
+status_t MidiPortRegistry::removeOutputPort(
+        AMIDI_OutputPort port,
+        AMIDI_Device *devicePtr,
+        sp<IBinder> *portTokenPtr) {
+    OutputPortMap::iterator itr = mOutputPortMap.find(port);
+    if (itr == mOutputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    OutputPortEntry *entry = mOutputPortMap[port];
+    int portState = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+    while (!entry->state.compare_exchange_weak(portState, MIDI_OUTPUT_PORT_STATE_CLOSED)) {
+        if (portState == MIDI_OUTPUT_PORT_STATE_CLOSED) {
+            return -EINVAL; // Already closed
+        }
+    }
+    *devicePtr = entry->port->device;
+    *portTokenPtr = entry->port->binderToken;
+    delete entry->port;
+    entry->port = nullptr;
+
+    mOutputPortMap.erase(itr);
+
+    return OK;
+}
+
+status_t MidiPortRegistry::getOutputPortFdAndLock(
+        AMIDI_OutputPort port, base::unique_fd **ufdPtr) {
+    if (mOutputPortMap.find(port) == mOutputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    OutputPortEntry *entry = mOutputPortMap[port];
+    int portState = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+    if (!entry->state.compare_exchange_strong(portState, MIDI_OUTPUT_PORT_STATE_OPEN_ACTIVE)) {
+        // The port has been closed.
+        return -EPIPE;
+    }
+    *ufdPtr = &entry->port->ufd;
+
+    return OK;
+}
+
+status_t MidiPortRegistry::unlockOutputPort(AMIDI_OutputPort port) {
+    if (mOutputPortMap.find(port) == mOutputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    OutputPortEntry *entry = mOutputPortMap[port];
+    entry->state.store(MIDI_OUTPUT_PORT_STATE_OPEN_IDLE);
+    return OK;
+}
+
+status_t MidiPortRegistry::addInputPort(
+        AMIDI_Device device,
+        sp<IBinder> portToken,
+        base::unique_fd &&ufd,
+        AMIDI_InputPort *portPtr) {
+    *portPtr = mNextInputPortToken++;
+
+    InputPortEntry *entry = new InputPortEntry;
+
+    entry->state = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+    entry->port = new InputPort;
+    entry->port->device = device;
+    entry->port->binderToken = portToken;
+    entry->port->ufd = std::move(ufd);
+
+    mInputPortMap[*portPtr] = entry;
+
+    return OK;
+}
+
+status_t MidiPortRegistry::removeInputPort(
+        AMIDI_InputPort port,
+        AMIDI_Device *devicePtr,
+        sp<IBinder> *portTokenPtr) {
+    InputPortMap::iterator itr = mInputPortMap.find(port);
+    if (itr == mInputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    InputPortEntry *entry = mInputPortMap[port];
+    int portState = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+    while (!entry->state.compare_exchange_weak(portState, MIDI_INPUT_PORT_STATE_CLOSED)) {
+        if (portState == MIDI_INPUT_PORT_STATE_CLOSED) return -EINVAL; // Already closed
+    }
+
+    *devicePtr = entry->port->device;
+    *portTokenPtr = entry->port->binderToken;
+    delete entry->port;
+    entry->port = nullptr;
+
+    mInputPortMap.erase(itr);
+
+    return OK;
+}
+
+status_t MidiPortRegistry::getInputPortFd(AMIDI_InputPort port, base::unique_fd **ufdPtr) {
+    if (mInputPortMap.find(port) == mInputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    InputPortEntry *entry = mInputPortMap[port];
+
+    *ufdPtr = &entry->port->ufd;
+
+    return OK;
+}
+
+status_t MidiPortRegistry::getInputPortFdAndLock(AMIDI_InputPort port, base::unique_fd **ufdPtr) {
+    if (mInputPortMap.find(port) == mInputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    InputPortEntry *entry = mInputPortMap[port];
+
+    int portState = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+    if (!entry->state.compare_exchange_strong(portState, MIDI_INPUT_PORT_STATE_OPEN_ACTIVE)) {
+        // The port has been closed.
+        return -EPIPE;
+    }
+    *ufdPtr = &entry->port->ufd;
+    return OK;
+}
+
+status_t MidiPortRegistry::MidiPortRegistry::unlockInputPort(AMIDI_InputPort port) {
+    if (mInputPortMap.find(port) == mInputPortMap.end()) {
+        return -EINVAL;
+    }
+
+    InputPortEntry *entry = mInputPortMap[port];
+    entry->state.store(MIDI_INPUT_PORT_STATE_OPEN_IDLE);
+    return OK;
+}
+
+} // namespace midi
+} // namespace media
+} // namespace android
diff --git a/media/native/midi/MidiPortRegistry.h b/media/native/midi/MidiPortRegistry.h
new file mode 100644
index 0000000..f1ffb78
--- /dev/null
+++ b/media/native/midi/MidiPortRegistry.h
@@ -0,0 +1,192 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
+#define ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
+
+#include <atomic>
+#include <map>
+
+#include <android-base/unique_fd.h>
+#include <binder/IBinder.h>
+#include <utils/Errors.h>
+#include <utils/Singleton.h>
+
+#include "midi.h"
+
+namespace android {
+namespace media {
+namespace midi {
+
+/*
+ * Maintains lists of all active input and output MIDI ports and controls access to them. Provides
+ * exclusive access to specific MIDI ports.
+ */
+class MidiPortRegistry : public Singleton<MidiPortRegistry> {
+  public:
+    /*
+     * Creates an output port entry and associates it with the specified MIDI device.
+     * Called by AMIDI_openOutputPort();
+     *
+     * device       The native API device ID.
+     * portToken    The port token (returned from the device server).
+     * udf          File descriptor for the data port associated with the MIDI output port.
+     * portPtr      Receives the native API port ID of the port being opened.
+     */
+    status_t addOutputPort(
+            AMIDI_Device device,
+            sp<IBinder> portToken,
+            base::unique_fd &&ufd,
+            AMIDI_OutputPort *portPtr);
+
+    /*
+     * Removes for the output port list a previously added output port.
+     * Called by AMIDI_closeOutputPort();
+     *
+     * port         The native API port ID of the port being closed.
+     * devicePtr    Receives the native API device ID associated with the port.
+     * portTokenPtr Receives the binder token associated with the port.
+     */
+    status_t removeOutputPort(
+            AMIDI_OutputPort port,
+            AMIDI_Device *devicePtr,
+            sp<IBinder> *portTokenPtr);
+
+    /*
+     * Creates an input port entry and associates it with the specified MIDI device.
+     * Called by AMIDI_openInputPort();
+     *
+     * device       The native API device ID.
+     * portToken    The port token (returned from the device server).
+     * udf          File descriptor for the data port associated with the MIDI input port.
+     * portPtr      Receives the native API port ID of the port being opened.
+     */
+    status_t addInputPort(
+            AMIDI_Device device,
+            sp<IBinder> portToken,
+            base::unique_fd &&ufd,
+            AMIDI_InputPort *portPtr);
+
+    /*
+     * Removes for the input port list a previously added input port.
+     * Called by AMIDI_closeINputPort();
+     *
+     * port         The native API port ID of the port being closed.
+     * devicePtr    Receives the native API device ID associated with the port.
+     * portTokenPtr Receives the binder token associated with the port.
+     */
+    status_t removeInputPort(
+            AMIDI_InputPort port,
+            AMIDI_Device *devicePtr,
+            sp<IBinder> *portTokenPtr);
+
+    /*
+     * Retrieves an exclusive-access file descriptor for an output port.
+     * Called from AMIDI_receive().
+     *
+     * port     The native API id of the output port.
+     * ufdPtr   Receives the exclusive-access file descriptor for the output port.
+     */
+    status_t getOutputPortFdAndLock(AMIDI_OutputPort port, base::unique_fd **ufdPtr);
+
+    /*
+     * Releases exclusive-access to the port and invalidates the previously received file
+     * descriptor.
+     * Called from AMIDI_receive().
+     *
+     * port The native API id of the output port.
+     */
+    status_t unlockOutputPort(AMIDI_OutputPort port);
+
+    /*
+     * Retrieves an exclusive-access file descriptor for an input port.
+     * (Not being used as (perhaps) AMIDI_sendWithTimestamp() doesn't need exclusive access
+     * to the port).
+     *
+     * port     The native API id of the input port.
+     * ufdPtr   Receives the exclusive-access file descriptor for the input port.
+     */
+    status_t getInputPortFdAndLock(AMIDI_InputPort port, base::unique_fd **ufdPtr);
+
+    /*
+     * Releases exclusive-access to the port and invalidates the previously received file
+     * descriptor.
+     * (Not used. See above).
+     *
+     * port The native API id of the input port.
+     */
+    status_t unlockInputPort(AMIDI_InputPort port);
+
+    /*
+     * Retrieves an unlocked (multi-access) file descriptor for an input port.
+     * Used by AMIDI_sendWith(), AMIDI_sendWithTimestamp & AMIDI_flush.
+     *
+     * port     The native API id of the input port.
+     * ufdPtr   Receives the multi-access file descriptor for the input port.
+     */
+    status_t getInputPortFd(AMIDI_InputPort port, base::unique_fd **ufdPtr);
+
+  private:
+    friend class Singleton<MidiPortRegistry>;
+    MidiPortRegistry();
+
+    /*
+     * Output (data receiving) ports.
+     */
+    struct OutputPort;
+    enum {
+        MIDI_OUTPUT_PORT_STATE_CLOSED = 0,
+        MIDI_OUTPUT_PORT_STATE_OPEN_IDLE,
+        MIDI_OUTPUT_PORT_STATE_OPEN_ACTIVE
+    };
+
+    struct OutputPortEntry {
+        std::atomic_int state;
+        OutputPort *port;
+    };
+
+    typedef std::map<AMIDI_OutputPort, OutputPortEntry*> OutputPortMap;
+    // Access is synchronized per record via 'state' field.
+    std::atomic<AMIDI_OutputPort> mNextOutputPortToken;
+    OutputPortMap  mOutputPortMap;
+
+    /*
+     * Input (data sending) ports.
+     */
+    struct InputPort;
+    enum {
+        MIDI_INPUT_PORT_STATE_CLOSED = 0,
+        MIDI_INPUT_PORT_STATE_OPEN_IDLE,
+        MIDI_INPUT_PORT_STATE_OPEN_ACTIVE
+    };
+
+    struct InputPortEntry {
+        std::atomic_int state;
+        InputPort *port;
+    };
+
+    typedef std::map<AMIDI_OutputPort, InputPortEntry*> InputPortMap;
+    // Access is synchronized per record via 'state' field.
+    std::atomic<AMIDI_InputPort> mNextInputPortToken;
+    InputPortMap  mInputPortMap;
+
+};
+
+} // namespace midi
+} // namespace media
+} // namespace android
+
+#endif // ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
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;
+}
+
diff --git a/media/native/midi/midi.h b/media/native/midi/midi.h
new file mode 100644
index 0000000..717bc66
--- /dev/null
+++ b/media/native/midi/midi.h
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_H_
+#define ANDROID_MEDIA_MIDI_H_
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+
+using android::status_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//typedef struct _AMIDI_Device;
+//typedef struct _AMIDI_InputPort;
+//typedef struct _AMIDI_OutputPort;
+//typedef _AMIDI_Device*      AMIDI_Device;
+//typedef _AMIDI_InputPort*   AMIDI_InputPort;
+//typedef _AMIDI_OutputPort*  AMIDI_OutputPort;
+
+typedef int32_t AMIDI_Device;
+typedef int32_t AMIDI_InputPort;
+typedef int32_t  AMIDI_OutputPort;
+
+//TODO - Do we want to wrap this stuff in namespace android { namespace media { namespace midi {?
+
+enum {
+    AMIDI_INVALID_HANDLE = -1
+};
+
+enum {
+    AMIDI_OPCODE_DATA = 1,
+    AMIDI_OPCODE_FLUSH = 2,
+    AMIDI_PACKET_SIZE = 1024,  /* !!! Currently MidiPortImpl.MAX_PACKET_SIZE !!! */
+    AMIDI_PACKET_OVERHEAD = 9,
+    AMIDI_BUFFER_SIZE = AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD
+            /* !!! TBD, currently MidiPortImpl.MAX_PACKET_DATA_SIZE !!! */
+};
+
+typedef struct {
+    uint32_t opcode;
+    uint8_t buffer[AMIDI_BUFFER_SIZE];
+    size_t len;
+    int64_t timestamp;
+} AMIDI_Message;
+
+enum {
+    AMIDI_DEVICE_TYPE_USB = 1,
+    AMIDI_DEVICE_TYPE_VIRTUAL = 2,
+    AMIDI_DEVICE_TYPE_BLUETOOTH = 3
+};
+
+typedef struct {
+    int32_t type;
+    int32_t uid;
+    int32_t isPrivate;
+    int32_t inputPortCount;
+    int32_t outputPortCount;
+} AMIDI_DeviceInfo;
+
+/*
+ * Device API
+ */
+/*
+ * Retrieves the native API device token for the specified Java API device ID.
+ *
+ * uid          The Java API id of the device.
+ * devicePtr    Receives the associated native API token for the device.
+ *
+ * Returns OK or a (negative) error code.
+ */
+status_t AMIDI_getDeviceById(int32_t id, AMIDI_Device *devicePtr);
+
+/*
+ * Retrieves information for the native MIDI device.
+ *
+ * device           The Native API token for the device.
+ * deviceInfoPtr    Receives the associated device info.
+ *
+ * Returns OK or a (negative) error code.
+ */
+status_t AMIDI_getDeviceInfo(AMIDI_Device device, AMIDI_DeviceInfo *deviceInfoPtr);
+
+/*
+ * API for receiving data from the Output port of a device.
+ */
+/*
+ * Opens the output port.
+ *
+ * device           Identifies the device.
+ * portNumber       Specifies the zero-based port index on the device to open.
+ * outputPortPtr    Receives the native API port identifier of the opened port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_openOutputPort(AMIDI_Device device, int portNumber, AMIDI_OutputPort *outputPortPtr);
+
+/*
+ * Receives any pending MIDI messages (up to the specified maximum number of messages).
+ *
+ * outputPort   Identifies the port to receive messages from.
+ * messages     Points to an array (size maxMessages) to receive the MIDI messages.
+ * maxMessages  The number of messages allocated in the messages array.
+ *
+ * Returns the number of messages received, or a (negative) error code.
+ */
+ssize_t AMIDI_receive(AMIDI_OutputPort outputPort, AMIDI_Message *messages, ssize_t maxMessages);
+
+/*
+ * Closes the output port.
+ *
+ * outputPort   The native API port identifier of the port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_closeOutputPort(AMIDI_OutputPort outputPort);
+
+/*
+ * API for sending data to the Input port of a device.
+ */
+/*
+ * Opens the input port.
+ *
+ * device           Identifies the device.
+ * portNumber       Specifies the zero-based port index on the device to open.
+ * inputPortPtr     Receives the native API port identifier of the opened port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_openInputPort(AMIDI_Device device, int portNumber, AMIDI_InputPort *inputPortPtr);
+
+/*
+ * Returns the maximum number of bytes that can be received in a single MIDI message.
+ */
+ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort inputPort);
+
+/*
+ * Sends data to the specified input port.
+ *
+ * inputPort    The native API identifier of the port to send data to.
+ * buffer       Points to the array of bytes containing the data to send.
+ * numBytes     Specifies the number of bytes to write.
+ *
+ * Returns  The number of bytes sent or a (negative) error code.
+ */
+ssize_t AMIDI_send(AMIDI_InputPort inputPort, uint8_t *buffer, ssize_t numBytes);
+
+/*
+ * Sends data to the specified input port with a timestamp.
+ *
+ * inputPort    The native API identifier of the port to send data to.
+ * buffer       Points to the array of bytes containing the data to send.
+ * numBytes     Specifies the number of bytes to write.
+ * timestamp    The time stamp to associate with the sent data.
+ *
+ * Returns  The number of bytes sent or a (negative) error code.
+ */
+ssize_t AMIDI_sendWithTimestamp(AMIDI_InputPort inputPort, uint8_t *buffer,
+        ssize_t numBytes, int64_t timestamp);
+
+/*
+ * Sends a message with a 'MIDI flush command code' to the specified port.
+ *
+ * inputPort    The native API identifier of the port to send the flush message to.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_flush(AMIDI_InputPort inputPort);
+
+/*
+ * Closes the input port.
+ *
+ * inputPort   The native API port identifier of the port.
+ *
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_closeInputPort(AMIDI_InputPort inputPort);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ANDROID_MEDIA_MIDI_H_ */