1f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood/*
2f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * Copyright (C) 2015 The Android Open Source Project
3f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood *
4f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * Licensed under the Apache License, Version 2.0 (the "License");
5f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * you may not use this file except in compliance with the License.
6f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * You may obtain a copy of the License at
7f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood *
8f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood *      http://www.apache.org/licenses/LICENSE-2.0
9f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood *
10f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * Unless required by applicable law or agreed to in writing, software
11f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * distributed under the License is distributed on an "AS IS" BASIS,
12f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * See the License for the specific language governing permissions and
14f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * limitations under the License.
15f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood */
16f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
17f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodpackage com.android.bluetoothmidiservice;
18f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
19f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.bluetooth.BluetoothDevice;
20f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.bluetooth.BluetoothGatt;
21f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.bluetooth.BluetoothGattCallback;
22f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.bluetooth.BluetoothGattCharacteristic;
23f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.bluetooth.BluetoothGattDescriptor;
24f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.bluetooth.BluetoothGattService;
25f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.bluetooth.BluetoothProfile;
26f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.content.Context;
27f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.media.midi.MidiDeviceInfo;
28e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwoodimport android.media.midi.MidiDeviceServer;
29e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwoodimport android.media.midi.MidiDeviceStatus;
30e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwoodimport android.media.midi.MidiManager;
31e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwoodimport android.media.midi.MidiReceiver;
32f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.os.Bundle;
33f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.os.IBinder;
34f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport android.util.Log;
35f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
36f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport com.android.internal.midi.MidiEventScheduler;
37f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport com.android.internal.midi.MidiEventScheduler.MidiEvent;
38f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
39f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport libcore.io.IoUtils;
40f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
41f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport java.io.IOException;
42f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport java.util.List;
43f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodimport java.util.UUID;
44f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
45f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood/**
46f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood * Class used to implement a Bluetooth MIDI device.
47f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood */
48f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwoodpublic final class BluetoothMidiDevice {
49f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
50f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private static final String TAG = "BluetoothMidiDevice";
518c26d843a786e5ee56046245fbf72a81b533bcb9Mike Lockwood    private static final boolean DEBUG = false;
52f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
53f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private static final int MAX_PACKET_SIZE = 20;
54f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
55f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    //  Bluetooth MIDI Gatt service UUID
56f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private static final UUID MIDI_SERVICE = UUID.fromString(
57f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            "03B80E5A-EDE8-4B33-A751-6CE34EC4C700");
58f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    // Bluetooth MIDI Gatt characteristic UUID
59f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private static final UUID MIDI_CHARACTERISTIC = UUID.fromString(
60f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            "7772E5DB-3868-4112-A1A9-F2669D106BF3");
61f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    // Descriptor UUID for enabling characteristic changed notifications
62f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString(
63f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            "00002902-0000-1000-8000-00805f9b34fb");
64f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
65f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private final BluetoothDevice mBluetoothDevice;
66f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private final BluetoothMidiService mService;
67f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private final MidiManager mMidiManager;
68f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private MidiReceiver mOutputReceiver;
69f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private final MidiEventScheduler mEventScheduler = new MidiEventScheduler();
70f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
71f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private MidiDeviceServer mDeviceServer;
72f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private BluetoothGatt mBluetoothGatt;
73f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
74f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private BluetoothGattCharacteristic mCharacteristic;
75f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
76f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    // PacketReceiver for receiving formatted packets from our BluetoothPacketEncoder
77f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private final PacketReceiver mPacketReceiver = new PacketReceiver();
78f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
79f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private final BluetoothPacketEncoder mPacketEncoder
80f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            = new BluetoothPacketEncoder(mPacketReceiver, MAX_PACKET_SIZE);
81f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
82f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private final BluetoothPacketDecoder mPacketDecoder
83f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            = new BluetoothPacketDecoder(MAX_PACKET_SIZE);
84f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
85e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood    private final MidiDeviceServer.Callback mDeviceServerCallback
86e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood            = new MidiDeviceServer.Callback() {
87e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        @Override
88e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
89e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        }
90e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood
91e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        @Override
92e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        public void onClose() {
93e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood            close();
94e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood        }
95e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood    };
96e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood
97f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
98f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        @Override
99f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        public void onConnectionStateChange(BluetoothGatt gatt, int status,
100f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                int newState) {
101f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            String intentAction;
102f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            if (newState == BluetoothProfile.STATE_CONNECTED) {
1031f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                Log.d(TAG, "Connected to GATT server.");
1041f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                Log.d(TAG, "Attempting to start service discovery:" +
105f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                        mBluetoothGatt.discoverServices());
106f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
107f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                Log.i(TAG, "Disconnected from GATT server.");
108f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                close();
109f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            }
110f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }
111f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
112f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        @Override
113f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
114f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            if (status == BluetoothGatt.GATT_SUCCESS) {
1151f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                BluetoothGattService service = gatt.getService(MIDI_SERVICE);
1161f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                if (service != null) {
1171f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                    Log.d(TAG, "found MIDI_SERVICE");
1181f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                    BluetoothGattCharacteristic characteristic
1191f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                            = service.getCharacteristic(MIDI_CHARACTERISTIC);
1201f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                    if (characteristic != null) {
1211f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                        Log.d(TAG, "found MIDI_CHARACTERISTIC");
1221f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                        mCharacteristic = characteristic;
1231f99a3277ba39f65195b953c6f82fc323d925850Phil Burk
1241f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                        // Request a lower Connection Interval for better latency.
1251f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                        boolean result = gatt.requestConnectionPriority(
1261f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                                BluetoothGatt.CONNECTION_PRIORITY_HIGH);
1271f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                        Log.d(TAG, "requestConnectionPriority(CONNECTION_PRIORITY_HIGH):"
1281f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                            + result);
1291f99a3277ba39f65195b953c6f82fc323d925850Phil Burk
1301f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                        // Specification says to read the characteristic first and then
1311f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                        // switch to receiving notifications
1321f99a3277ba39f65195b953c6f82fc323d925850Phil Burk                        mBluetoothGatt.readCharacteristic(characteristic);
133f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    }
134f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                }
135f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            } else {
1369490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood                Log.e(TAG, "onServicesDiscovered received: " + status);
1379490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood                close();
138f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            }
139f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }
140f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
141f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        @Override
142f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        public void onCharacteristicRead(BluetoothGatt gatt,
143f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                BluetoothGattCharacteristic characteristic,
144f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                int status) {
145f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            Log.d(TAG, "onCharacteristicRead " + status);
146f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
147f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            // switch to receiving notifications after initial characteristic read
148f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            mBluetoothGatt.setCharacteristicNotification(characteristic, true);
149f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
15095129f50c68c734c5ebdf32ff6b8b9c63cc1ada7Phil Burk            // Use writeType that requests acknowledgement.
15195129f50c68c734c5ebdf32ff6b8b9c63cc1ada7Phil Burk            // This improves compatibility with various BLE-MIDI devices.
15295129f50c68c734c5ebdf32ff6b8b9c63cc1ada7Phil Burk            int originalWriteType = characteristic.getWriteType();
15395129f50c68c734c5ebdf32ff6b8b9c63cc1ada7Phil Burk            characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
15495129f50c68c734c5ebdf32ff6b8b9c63cc1ada7Phil Burk
155f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
156f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    CLIENT_CHARACTERISTIC_CONFIG);
1579490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood            if (descriptor != null) {
1589490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
15995129f50c68c734c5ebdf32ff6b8b9c63cc1ada7Phil Burk                boolean result = mBluetoothGatt.writeDescriptor(descriptor);
16095129f50c68c734c5ebdf32ff6b8b9c63cc1ada7Phil Burk                Log.d(TAG, "writeDescriptor returned " + result);
1619490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood            } else {
1629490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood                Log.e(TAG, "No CLIENT_CHARACTERISTIC_CONFIG for device " + mBluetoothDevice);
1639490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood            }
16495129f50c68c734c5ebdf32ff6b8b9c63cc1ada7Phil Burk
16595129f50c68c734c5ebdf32ff6b8b9c63cc1ada7Phil Burk            characteristic.setWriteType(originalWriteType);
166f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }
167f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
168f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        @Override
169f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        public void onCharacteristicWrite(BluetoothGatt gatt,
170f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                BluetoothGattCharacteristic characteristic,
171f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                int status) {
172f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            Log.d(TAG, "onCharacteristicWrite " + status);
173f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            mPacketEncoder.writeComplete();
174f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }
175f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
176f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        @Override
177f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        public void onCharacteristicChanged(BluetoothGatt gatt,
178f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                                            BluetoothGattCharacteristic characteristic) {
1798c26d843a786e5ee56046245fbf72a81b533bcb9Mike Lockwood            if (DEBUG) {
1808c26d843a786e5ee56046245fbf72a81b533bcb9Mike Lockwood                logByteArray("Received ", characteristic.getValue(), 0,
1818c26d843a786e5ee56046245fbf72a81b533bcb9Mike Lockwood                        characteristic.getValue().length);
1828c26d843a786e5ee56046245fbf72a81b533bcb9Mike Lockwood            }
183f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver);
184f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }
185f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    };
186f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
187f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    // This receives MIDI data that has already been passed through our MidiEventScheduler
188f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    // and has been normalized by our MidiFramer.
189f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
190f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private class PacketReceiver implements PacketEncoder.PacketReceiver {
191f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        // buffers of every possible packet size
192f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        private final byte[][] mWriteBuffers;
193f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
194f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        public PacketReceiver() {
195f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            // Create buffers of every possible packet size
196f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            mWriteBuffers = new byte[MAX_PACKET_SIZE + 1][];
197f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            for (int i = 0; i <= MAX_PACKET_SIZE; i++) {
198f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                mWriteBuffers[i] = new byte[i];
199f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            }
200f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }
201f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
202f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        @Override
203f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        public void writePacket(byte[] buffer, int count) {
204f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            if (mCharacteristic == null) {
205f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                Log.w(TAG, "not ready to send packet yet");
206f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                return;
207f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            }
208f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            byte[] writeBuffer = mWriteBuffers[count];
209f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            System.arraycopy(buffer, 0, writeBuffer, 0, count);
210f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            mCharacteristic.setValue(writeBuffer);
2118c26d843a786e5ee56046245fbf72a81b533bcb9Mike Lockwood            if (DEBUG) {
2128c26d843a786e5ee56046245fbf72a81b533bcb9Mike Lockwood                logByteArray("Sent ", mCharacteristic.getValue(), 0,
2138c26d843a786e5ee56046245fbf72a81b533bcb9Mike Lockwood                       mCharacteristic.getValue().length);
2148c26d843a786e5ee56046245fbf72a81b533bcb9Mike Lockwood            }
215f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            mBluetoothGatt.writeCharacteristic(mCharacteristic);
216f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }
217f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    }
218f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
219f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    public BluetoothMidiDevice(Context context, BluetoothDevice device,
220f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            BluetoothMidiService service) {
221f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        mBluetoothDevice = device;
222f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        mService = service;
223f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
224f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback);
225f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
226f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        mMidiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
227f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
228f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        Bundle properties = new Bundle();
229f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        properties.putString(MidiDeviceInfo.PROPERTY_NAME, mBluetoothGatt.getDevice().getName());
230f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        properties.putParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE,
231f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                mBluetoothGatt.getDevice());
232f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
233f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        MidiReceiver[] inputPortReceivers = new MidiReceiver[1];
234f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        inputPortReceivers[0] = mEventScheduler.getReceiver();
235f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
236f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1,
237e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood                null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, mDeviceServerCallback);
238f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
239f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0];
240f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
241f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        // This thread waits for outgoing messages from our MidiEventScheduler
242f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        // And forwards them to our MidiFramer to be prepared to send via Bluetooth.
243f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        new Thread("BluetoothMidiDevice " + mBluetoothDevice) {
244f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            @Override
245f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            public void run() {
246f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                while (true) {
247f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    MidiEvent event;
248f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    try {
249f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                        event = (MidiEvent)mEventScheduler.waitNextEvent();
250f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    } catch (InterruptedException e) {
251f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                        // try again
252f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                        continue;
253f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    }
254f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    if (event == null) {
255f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                        break;
256f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    }
257f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    try {
2587eb441cb4abcd3230a4d243469c5044f49e707c8Mike Lockwood                        mPacketEncoder.send(event.data, 0, event.count,
259f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                                event.getTimestamp());
260f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    } catch (IOException e) {
2617eb441cb4abcd3230a4d243469c5044f49e707c8Mike Lockwood                        Log.e(TAG, "mPacketAccumulator.send failed", e);
262f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    }
263f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                    mEventScheduler.addEventToPool(event);
264f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                }
265f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                Log.d(TAG, "BluetoothMidiDevice thread exit");
266f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            }
267f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }.start();
268f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    }
269f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
2709490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood    private void close() {
2719490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood        synchronized (mBluetoothDevice) {
272e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood            mEventScheduler.close();
273e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood            mService.deviceClosed(mBluetoothDevice);
274e0a6ca64fac5bd4f10139321604031816e90adb4Mike Lockwood
2759490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood            if (mDeviceServer != null) {
2769490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood                IoUtils.closeQuietly(mDeviceServer);
2779490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood                mDeviceServer = null;
2789490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood            }
2799490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood            if (mBluetoothGatt != null) {
2809490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood                mBluetoothGatt.close();
2819490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood                mBluetoothGatt = null;
2829490eae3aa6b20cbd3c1557fd3a7eb927e12907fMike Lockwood            }
283f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }
284f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    }
285f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
286f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    public IBinder getBinder() {
287f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        return mDeviceServer.asBinder();
288f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    }
289f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood
290f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    private static void logByteArray(String prefix, byte[] value, int offset, int count) {
291f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        StringBuilder builder = new StringBuilder(prefix);
292f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        for (int i = offset; i < count; i++) {
2938c26d843a786e5ee56046245fbf72a81b533bcb9Mike Lockwood            builder.append(String.format("0x%02X", value[i]));
294f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            if (i != value.length - 1) {
295f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood                builder.append(", ");
296f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood            }
297f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        }
298f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood        Log.d(TAG, builder.toString());
299f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood    }
300f0a41d1c591193fbe02c9ddbaf24c79af4da9972Mike Lockwood}
301