BluetoothMidiDevice.java revision 9490eae3aa6b20cbd3c1557fd3a7eb927e12907f
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 close(); 96 } 97 } 98 99 @Override 100 public void onServicesDiscovered(BluetoothGatt gatt, int status) { 101 if (status == BluetoothGatt.GATT_SUCCESS) { 102 List<BluetoothGattService> services = mBluetoothGatt.getServices(); 103 for (BluetoothGattService service : services) { 104 if (MIDI_SERVICE.equals(service.getUuid())) { 105 Log.d(TAG, "found MIDI_SERVICE"); 106 List<BluetoothGattCharacteristic> characteristics 107 = service.getCharacteristics(); 108 for (BluetoothGattCharacteristic characteristic : characteristics) { 109 if (MIDI_CHARACTERISTIC.equals(characteristic.getUuid())) { 110 Log.d(TAG, "found MIDI_CHARACTERISTIC"); 111 mCharacteristic = characteristic; 112 113 // Specification says to read the characteristic first and then 114 // switch to receiving notifications 115 mBluetoothGatt.readCharacteristic(characteristic); 116 break; 117 } 118 } 119 break; 120 } 121 } 122 } else { 123 Log.e(TAG, "onServicesDiscovered received: " + status); 124 close(); 125 } 126 } 127 128 @Override 129 public void onCharacteristicRead(BluetoothGatt gatt, 130 BluetoothGattCharacteristic characteristic, 131 int status) { 132 Log.d(TAG, "onCharacteristicRead " + status); 133 134 // switch to receiving notifications after initial characteristic read 135 mBluetoothGatt.setCharacteristicNotification(characteristic, true); 136 137 BluetoothGattDescriptor descriptor = characteristic.getDescriptor( 138 CLIENT_CHARACTERISTIC_CONFIG); 139 if (descriptor != null) { 140 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 141 mBluetoothGatt.writeDescriptor(descriptor); 142 } else { 143 Log.e(TAG, "No CLIENT_CHARACTERISTIC_CONFIG for device " + mBluetoothDevice); 144 } 145 } 146 147 @Override 148 public void onCharacteristicWrite(BluetoothGatt gatt, 149 BluetoothGattCharacteristic characteristic, 150 int status) { 151 Log.d(TAG, "onCharacteristicWrite " + status); 152 mPacketEncoder.writeComplete(); 153 } 154 155 @Override 156 public void onCharacteristicChanged(BluetoothGatt gatt, 157 BluetoothGattCharacteristic characteristic) { 158 if (DEBUG) { 159 logByteArray("Received ", characteristic.getValue(), 0, 160 characteristic.getValue().length); 161 } 162 mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver); 163 } 164 }; 165 166 // This receives MIDI data that has already been passed through our MidiEventScheduler 167 // and has been normalized by our MidiFramer. 168 169 private class PacketReceiver implements PacketEncoder.PacketReceiver { 170 // buffers of every possible packet size 171 private final byte[][] mWriteBuffers; 172 173 public PacketReceiver() { 174 // Create buffers of every possible packet size 175 mWriteBuffers = new byte[MAX_PACKET_SIZE + 1][]; 176 for (int i = 0; i <= MAX_PACKET_SIZE; i++) { 177 mWriteBuffers[i] = new byte[i]; 178 } 179 } 180 181 @Override 182 public void writePacket(byte[] buffer, int count) { 183 if (mCharacteristic == null) { 184 Log.w(TAG, "not ready to send packet yet"); 185 return; 186 } 187 byte[] writeBuffer = mWriteBuffers[count]; 188 System.arraycopy(buffer, 0, writeBuffer, 0, count); 189 mCharacteristic.setValue(writeBuffer); 190 if (DEBUG) { 191 logByteArray("Sent ", mCharacteristic.getValue(), 0, 192 mCharacteristic.getValue().length); 193 } 194 mBluetoothGatt.writeCharacteristic(mCharacteristic); 195 } 196 } 197 198 public BluetoothMidiDevice(Context context, BluetoothDevice device, 199 BluetoothMidiService service) { 200 mBluetoothDevice = device; 201 mService = service; 202 203 mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback); 204 205 mMidiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 206 207 Bundle properties = new Bundle(); 208 properties.putString(MidiDeviceInfo.PROPERTY_NAME, mBluetoothGatt.getDevice().getName()); 209 properties.putParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE, 210 mBluetoothGatt.getDevice()); 211 212 MidiReceiver[] inputPortReceivers = new MidiReceiver[1]; 213 inputPortReceivers[0] = mEventScheduler.getReceiver(); 214 215 mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1, 216 null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, null); 217 218 mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0]; 219 220 // This thread waits for outgoing messages from our MidiEventScheduler 221 // And forwards them to our MidiFramer to be prepared to send via Bluetooth. 222 new Thread("BluetoothMidiDevice " + mBluetoothDevice) { 223 @Override 224 public void run() { 225 while (true) { 226 MidiEvent event; 227 try { 228 event = (MidiEvent)mEventScheduler.waitNextEvent(); 229 } catch (InterruptedException e) { 230 // try again 231 continue; 232 } 233 if (event == null) { 234 break; 235 } 236 try { 237 mPacketEncoder.sendWithTimestamp(event.data, 0, event.count, 238 event.getTimestamp()); 239 } catch (IOException e) { 240 Log.e(TAG, "mPacketAccumulator.sendWithTimestamp failed", e); 241 } 242 mEventScheduler.addEventToPool(event); 243 } 244 Log.d(TAG, "BluetoothMidiDevice thread exit"); 245 } 246 }.start(); 247 } 248 249 private void close() { 250 synchronized (mBluetoothDevice) { 251 mEventScheduler.close(); 252 if (mDeviceServer != null) { 253 IoUtils.closeQuietly(mDeviceServer); 254 mDeviceServer = null; 255 mService.deviceClosed(mBluetoothDevice); 256 } 257 if (mBluetoothGatt != null) { 258 mBluetoothGatt.close(); 259 mBluetoothGatt = null; 260 } 261 } 262 } 263 264 public IBinder getBinder() { 265 return mDeviceServer.asBinder(); 266 } 267 268 private static void logByteArray(String prefix, byte[] value, int offset, int count) { 269 StringBuilder builder = new StringBuilder(prefix); 270 for (int i = offset; i < count; i++) { 271 builder.append(String.format("0x%02X", value[i])); 272 if (i != value.length - 1) { 273 builder.append(", "); 274 } 275 } 276 Log.d(TAG, builder.toString()); 277 } 278} 279