BluetoothMidiDevice.java revision 8c26d843a786e5ee56046245fbf72a81b533bcb9
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 17package com.android.bluetoothmidiservice; 18 19import android.bluetooth.BluetoothDevice; 20import android.bluetooth.BluetoothGatt; 21import android.bluetooth.BluetoothGattCallback; 22import android.bluetooth.BluetoothGattCharacteristic; 23import android.bluetooth.BluetoothGattDescriptor; 24import android.bluetooth.BluetoothGattService; 25import android.bluetooth.BluetoothProfile; 26import android.content.Context; 27import android.media.midi.MidiReceiver; 28import android.media.midi.MidiManager; 29import android.media.midi.MidiDeviceServer; 30import android.media.midi.MidiDeviceInfo; 31import android.os.Bundle; 32import android.os.IBinder; 33import android.util.Log; 34 35import com.android.internal.midi.MidiEventScheduler; 36import com.android.internal.midi.MidiEventScheduler.MidiEvent; 37 38import libcore.io.IoUtils; 39 40import java.io.IOException; 41import java.util.List; 42import java.util.UUID; 43 44/** 45 * Class used to implement a Bluetooth MIDI device. 46 */ 47public final class BluetoothMidiDevice { 48 49 private static final String TAG = "BluetoothMidiDevice"; 50 private static final boolean DEBUG = false; 51 52 private static final int MAX_PACKET_SIZE = 20; 53 54 // Bluetooth MIDI Gatt service UUID 55 private static final UUID MIDI_SERVICE = UUID.fromString( 56 "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"); 57 // Bluetooth MIDI Gatt characteristic UUID 58 private static final UUID MIDI_CHARACTERISTIC = UUID.fromString( 59 "7772E5DB-3868-4112-A1A9-F2669D106BF3"); 60 // Descriptor UUID for enabling characteristic changed notifications 61 private static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString( 62 "00002902-0000-1000-8000-00805f9b34fb"); 63 64 private final BluetoothDevice mBluetoothDevice; 65 private final BluetoothMidiService mService; 66 private final MidiManager mMidiManager; 67 private MidiReceiver mOutputReceiver; 68 private final MidiEventScheduler mEventScheduler = new MidiEventScheduler(); 69 70 private MidiDeviceServer mDeviceServer; 71 private BluetoothGatt mBluetoothGatt; 72 73 private BluetoothGattCharacteristic mCharacteristic; 74 75 // PacketReceiver for receiving formatted packets from our BluetoothPacketEncoder 76 private final PacketReceiver mPacketReceiver = new PacketReceiver(); 77 78 private final BluetoothPacketEncoder mPacketEncoder 79 = new BluetoothPacketEncoder(mPacketReceiver, MAX_PACKET_SIZE); 80 81 private final BluetoothPacketDecoder mPacketDecoder 82 = new BluetoothPacketDecoder(MAX_PACKET_SIZE); 83 84 private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { 85 @Override 86 public void onConnectionStateChange(BluetoothGatt gatt, int status, 87 int newState) { 88 String intentAction; 89 if (newState == BluetoothProfile.STATE_CONNECTED) { 90 Log.i(TAG, "Connected to GATT server."); 91 Log.i(TAG, "Attempting to start service discovery:" + 92 mBluetoothGatt.discoverServices()); 93 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 94 Log.i(TAG, "Disconnected from GATT server."); 95 // FIXME synchronize? 96 close(); 97 } 98 } 99 100 @Override 101 public void onServicesDiscovered(BluetoothGatt gatt, int status) { 102 if (status == BluetoothGatt.GATT_SUCCESS) { 103 List<BluetoothGattService> services = mBluetoothGatt.getServices(); 104 for (BluetoothGattService service : services) { 105 if (MIDI_SERVICE.equals(service.getUuid())) { 106 Log.d(TAG, "found MIDI_SERVICE"); 107 List<BluetoothGattCharacteristic> characteristics 108 = service.getCharacteristics(); 109 for (BluetoothGattCharacteristic characteristic : characteristics) { 110 if (MIDI_CHARACTERISTIC.equals(characteristic.getUuid())) { 111 Log.d(TAG, "found MIDI_CHARACTERISTIC"); 112 mCharacteristic = characteristic; 113 114 // Specification says to read the characteristic first and then 115 // switch to receiving notifications 116 mBluetoothGatt.readCharacteristic(characteristic); 117 break; 118 } 119 } 120 break; 121 } 122 } 123 } else { 124 Log.w(TAG, "onServicesDiscovered received: " + status); 125 // FIXME - report error back to client? 126 } 127 } 128 129 @Override 130 public void onCharacteristicRead(BluetoothGatt gatt, 131 BluetoothGattCharacteristic characteristic, 132 int status) { 133 Log.d(TAG, "onCharacteristicRead " + status); 134 135 // switch to receiving notifications after initial characteristic read 136 mBluetoothGatt.setCharacteristicNotification(characteristic, true); 137 138 BluetoothGattDescriptor descriptor = characteristic.getDescriptor( 139 CLIENT_CHARACTERISTIC_CONFIG); 140 // FIXME null check 141 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 142 mBluetoothGatt.writeDescriptor(descriptor); 143 } 144 145 @Override 146 public void onCharacteristicWrite(BluetoothGatt gatt, 147 BluetoothGattCharacteristic characteristic, 148 int status) { 149 Log.d(TAG, "onCharacteristicWrite " + status); 150 mPacketEncoder.writeComplete(); 151 } 152 153 @Override 154 public void onCharacteristicChanged(BluetoothGatt gatt, 155 BluetoothGattCharacteristic characteristic) { 156 if (DEBUG) { 157 logByteArray("Received ", characteristic.getValue(), 0, 158 characteristic.getValue().length); 159 } 160 mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver); 161 } 162 }; 163 164 // This receives MIDI data that has already been passed through our MidiEventScheduler 165 // and has been normalized by our MidiFramer. 166 167 private class PacketReceiver implements PacketEncoder.PacketReceiver { 168 // buffers of every possible packet size 169 private final byte[][] mWriteBuffers; 170 171 public PacketReceiver() { 172 // Create buffers of every possible packet size 173 mWriteBuffers = new byte[MAX_PACKET_SIZE + 1][]; 174 for (int i = 0; i <= MAX_PACKET_SIZE; i++) { 175 mWriteBuffers[i] = new byte[i]; 176 } 177 } 178 179 @Override 180 public void writePacket(byte[] buffer, int count) { 181 if (mCharacteristic == null) { 182 Log.w(TAG, "not ready to send packet yet"); 183 return; 184 } 185 byte[] writeBuffer = mWriteBuffers[count]; 186 System.arraycopy(buffer, 0, writeBuffer, 0, count); 187 mCharacteristic.setValue(writeBuffer); 188 if (DEBUG) { 189 logByteArray("Sent ", mCharacteristic.getValue(), 0, 190 mCharacteristic.getValue().length); 191 } 192 mBluetoothGatt.writeCharacteristic(mCharacteristic); 193 } 194 } 195 196 public BluetoothMidiDevice(Context context, BluetoothDevice device, 197 BluetoothMidiService service) { 198 mBluetoothDevice = device; 199 mService = service; 200 201 mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback); 202 203 mMidiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 204 205 Bundle properties = new Bundle(); 206 properties.putString(MidiDeviceInfo.PROPERTY_NAME, mBluetoothGatt.getDevice().getName()); 207 properties.putParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE, 208 mBluetoothGatt.getDevice()); 209 210 MidiReceiver[] inputPortReceivers = new MidiReceiver[1]; 211 inputPortReceivers[0] = mEventScheduler.getReceiver(); 212 213 mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1, 214 null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, null); 215 216 mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0]; 217 218 // This thread waits for outgoing messages from our MidiEventScheduler 219 // And forwards them to our MidiFramer to be prepared to send via Bluetooth. 220 new Thread("BluetoothMidiDevice " + mBluetoothDevice) { 221 @Override 222 public void run() { 223 while (true) { 224 MidiEvent event; 225 try { 226 event = (MidiEvent)mEventScheduler.waitNextEvent(); 227 } catch (InterruptedException e) { 228 // try again 229 continue; 230 } 231 if (event == null) { 232 break; 233 } 234 try { 235 mPacketEncoder.sendWithTimestamp(event.data, 0, event.count, 236 event.getTimestamp()); 237 } catch (IOException e) { 238 Log.e(TAG, "mPacketAccumulator.sendWithTimestamp failed", e); 239 } 240 mEventScheduler.addEventToPool(event); 241 } 242 Log.d(TAG, "BluetoothMidiDevice thread exit"); 243 } 244 }.start(); 245 } 246 247 void close() { 248 mEventScheduler.close(); 249 if (mDeviceServer != null) { 250 IoUtils.closeQuietly(mDeviceServer); 251 mDeviceServer = null; 252 mService.deviceClosed(mBluetoothDevice); 253 } 254 if (mBluetoothGatt != null) { 255 mBluetoothGatt.close(); 256 mBluetoothGatt = null; 257 } 258 } 259 260 public IBinder getBinder() { 261 return mDeviceServer.asBinder(); 262 } 263 264 private static void logByteArray(String prefix, byte[] value, int offset, int count) { 265 StringBuilder builder = new StringBuilder(prefix); 266 for (int i = offset; i < count; i++) { 267 builder.append(String.format("0x%02X", value[i])); 268 if (i != value.length - 1) { 269 builder.append(", "); 270 } 271 } 272 Log.d(TAG, builder.toString()); 273 } 274} 275