BluetoothMidiDevice.java revision f0a41d1c591193fbe02c9ddbaf24c79af4da9972
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 51 private static final int MAX_PACKET_SIZE = 20; 52 53 // Bluetooth MIDI Gatt service UUID 54 private static final UUID MIDI_SERVICE = UUID.fromString( 55 "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"); 56 // Bluetooth MIDI Gatt characteristic UUID 57 private static final UUID MIDI_CHARACTERISTIC = UUID.fromString( 58 "7772E5DB-3868-4112-A1A9-F2669D106BF3"); 59 // Descriptor UUID for enabling characteristic changed notifications 60 private static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString( 61 "00002902-0000-1000-8000-00805f9b34fb"); 62 63 private final BluetoothDevice mBluetoothDevice; 64 private final BluetoothMidiService mService; 65 private final MidiManager mMidiManager; 66 private MidiReceiver mOutputReceiver; 67 private final MidiEventScheduler mEventScheduler = new MidiEventScheduler(); 68 69 private MidiDeviceServer mDeviceServer; 70 private BluetoothGatt mBluetoothGatt; 71 72 private BluetoothGattCharacteristic mCharacteristic; 73 74 // PacketReceiver for receiving formatted packets from our BluetoothPacketEncoder 75 private final PacketReceiver mPacketReceiver = new PacketReceiver(); 76 77 private final BluetoothPacketEncoder mPacketEncoder 78 = new BluetoothPacketEncoder(mPacketReceiver, MAX_PACKET_SIZE); 79 80 private final BluetoothPacketDecoder mPacketDecoder 81 = new BluetoothPacketDecoder(MAX_PACKET_SIZE); 82 83 private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { 84 @Override 85 public void onConnectionStateChange(BluetoothGatt gatt, int status, 86 int newState) { 87 String intentAction; 88 if (newState == BluetoothProfile.STATE_CONNECTED) { 89 Log.i(TAG, "Connected to GATT server."); 90 Log.i(TAG, "Attempting to start service discovery:" + 91 mBluetoothGatt.discoverServices()); 92 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 93 Log.i(TAG, "Disconnected from GATT server."); 94 // FIXME synchronize? 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.w(TAG, "onServicesDiscovered received: " + status); 124 // FIXME - report error back to client? 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 // FIXME null check 140 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 141 mBluetoothGatt.writeDescriptor(descriptor); 142 } 143 144 @Override 145 public void onCharacteristicWrite(BluetoothGatt gatt, 146 BluetoothGattCharacteristic characteristic, 147 int status) { 148 Log.d(TAG, "onCharacteristicWrite " + status); 149 mPacketEncoder.writeComplete(); 150 } 151 152 @Override 153 public void onCharacteristicChanged(BluetoothGatt gatt, 154 BluetoothGattCharacteristic characteristic) { 155// logByteArray("Received ", characteristic.getValue(), 0, 156// characteristic.getValue().length); 157 mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver); 158 } 159 }; 160 161 // This receives MIDI data that has already been passed through our MidiEventScheduler 162 // and has been normalized by our MidiFramer. 163 164 private class PacketReceiver implements PacketEncoder.PacketReceiver { 165 // buffers of every possible packet size 166 private final byte[][] mWriteBuffers; 167 168 public PacketReceiver() { 169 // Create buffers of every possible packet size 170 mWriteBuffers = new byte[MAX_PACKET_SIZE + 1][]; 171 for (int i = 0; i <= MAX_PACKET_SIZE; i++) { 172 mWriteBuffers[i] = new byte[i]; 173 } 174 } 175 176 @Override 177 public void writePacket(byte[] buffer, int count) { 178 if (mCharacteristic == null) { 179 Log.w(TAG, "not ready to send packet yet"); 180 return; 181 } 182 byte[] writeBuffer = mWriteBuffers[count]; 183 System.arraycopy(buffer, 0, writeBuffer, 0, count); 184 mCharacteristic.setValue(writeBuffer); 185// logByteArray("Sent ", mCharacteristic.getValue(), 0, 186// mCharacteristic.getValue().length); 187 mBluetoothGatt.writeCharacteristic(mCharacteristic); 188 } 189 } 190 191 public BluetoothMidiDevice(Context context, BluetoothDevice device, 192 BluetoothMidiService service) { 193 mBluetoothDevice = device; 194 mService = service; 195 196 mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback); 197 198 mMidiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 199 200 Bundle properties = new Bundle(); 201 properties.putString(MidiDeviceInfo.PROPERTY_NAME, mBluetoothGatt.getDevice().getName()); 202 properties.putParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE, 203 mBluetoothGatt.getDevice()); 204 205 MidiReceiver[] inputPortReceivers = new MidiReceiver[1]; 206 inputPortReceivers[0] = mEventScheduler.getReceiver(); 207 208 mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1, 209 null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, null); 210 211 mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0]; 212 213 // This thread waits for outgoing messages from our MidiEventScheduler 214 // And forwards them to our MidiFramer to be prepared to send via Bluetooth. 215 new Thread("BluetoothMidiDevice " + mBluetoothDevice) { 216 @Override 217 public void run() { 218 while (true) { 219 MidiEvent event; 220 try { 221 event = (MidiEvent)mEventScheduler.waitNextEvent(); 222 } catch (InterruptedException e) { 223 // try again 224 continue; 225 } 226 if (event == null) { 227 break; 228 } 229 try { 230 mPacketEncoder.sendWithTimestamp(event.data, 0, event.count, 231 event.getTimestamp()); 232 } catch (IOException e) { 233 Log.e(TAG, "mPacketAccumulator.sendWithTimestamp failed", e); 234 } 235 mEventScheduler.addEventToPool(event); 236 } 237 Log.d(TAG, "BluetoothMidiDevice thread exit"); 238 } 239 }.start(); 240 } 241 242 void close() { 243 mEventScheduler.close(); 244 if (mDeviceServer != null) { 245 IoUtils.closeQuietly(mDeviceServer); 246 mDeviceServer = null; 247 mService.deviceClosed(mBluetoothDevice); 248 } 249 if (mBluetoothGatt != null) { 250 mBluetoothGatt.close(); 251 mBluetoothGatt = null; 252 } 253 } 254 255 public IBinder getBinder() { 256 return mDeviceServer.asBinder(); 257 } 258 259 private static void logByteArray(String prefix, byte[] value, int offset, int count) { 260 StringBuilder builder = new StringBuilder(prefix); 261 for (int i = offset; i < count; i++) { 262 String hex = Integer.toHexString(value[i]); 263 int length = hex.length(); 264 if (length == 1) { 265 hex = "0x" + hex; 266 } else { 267 hex = hex.substring(length - 2, length); 268 } 269 builder.append(hex); 270 if (i != value.length - 1) { 271 builder.append(", "); 272 } 273 } 274 Log.d(TAG, builder.toString()); 275 } 276} 277