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