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