1 /* 2 * Copyright (C) 2014 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 android.media.midi; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.RequiresFeature; 22 import android.annotation.SystemService; 23 import android.bluetooth.BluetoothDevice; 24 import android.content.Context; 25 import android.content.pm.PackageManager; 26 import android.os.Binder; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.os.RemoteException; 31 import android.util.ArraySet; 32 import android.util.Log; 33 34 import java.io.IOException; 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.util.Collections; 38 import java.util.Objects; 39 import java.util.Set; 40 import java.util.concurrent.ConcurrentHashMap; 41 import java.util.concurrent.Executor; 42 43 // BLE-MIDI 44 45 /** 46 * This class is the public application interface to the MIDI service. 47 */ 48 @SystemService(Context.MIDI_SERVICE) 49 @RequiresFeature(PackageManager.FEATURE_MIDI) 50 public final class MidiManager { 51 private static final String TAG = "MidiManager"; 52 53 /** 54 * Constant representing MIDI devices. 55 * These devices do NOT support Universal MIDI Packets by default. 56 * These support the original MIDI 1.0 byte stream. 57 * When communicating to a USB device, a raw byte stream will be padded for USB. 58 * Likewise, for a Bluetooth device, the raw bytes will be converted for Bluetooth. 59 * For virtual devices, the byte stream will be passed directly. 60 * If Universal MIDI Packets are needed, please use MIDI-CI. 61 * @see MidiManager#getDevicesForTransport 62 */ 63 public static final int TRANSPORT_MIDI_BYTE_STREAM = 1; 64 65 /** 66 * Constant representing Universal MIDI devices. 67 * These devices do support Universal MIDI Packets (UMP) by default. 68 * When sending data to these devices, please send UMP. 69 * Packets should always be a multiple of 4 bytes. 70 * UMP is defined in the USB MIDI 2.0 spec. Please read the standard for more info. 71 * @see MidiManager#getDevicesForTransport 72 */ 73 public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2; 74 75 /** 76 * @see MidiManager#getDevicesForTransport 77 * @hide 78 */ 79 @IntDef(prefix = { "TRANSPORT_" }, value = { 80 TRANSPORT_MIDI_BYTE_STREAM, 81 TRANSPORT_UNIVERSAL_MIDI_PACKETS 82 }) 83 @Retention(RetentionPolicy.SOURCE) 84 public @interface Transport {} 85 86 /** 87 * Intent for starting BluetoothMidiService 88 * @hide 89 */ 90 public static final String BLUETOOTH_MIDI_SERVICE_INTENT = 91 "android.media.midi.BluetoothMidiService"; 92 93 /** 94 * BluetoothMidiService package name 95 * @hide 96 */ 97 public static final String BLUETOOTH_MIDI_SERVICE_PACKAGE = "com.android.bluetoothmidiservice"; 98 99 /** 100 * BluetoothMidiService class name 101 * @hide 102 */ 103 public static final String BLUETOOTH_MIDI_SERVICE_CLASS = 104 "com.android.bluetoothmidiservice.BluetoothMidiService"; 105 106 private final IMidiManager mService; 107 private final IBinder mToken = new Binder(); 108 109 private ConcurrentHashMap<DeviceCallback,DeviceListener> mDeviceListeners = 110 new ConcurrentHashMap<DeviceCallback,DeviceListener>(); 111 112 // Binder stub for receiving device notifications from MidiService 113 private class DeviceListener extends IMidiDeviceListener.Stub { 114 private final DeviceCallback mCallback; 115 private final Executor mExecutor; 116 private final int mTransport; 117 DeviceListener(DeviceCallback callback, Executor executor, int transport)118 DeviceListener(DeviceCallback callback, Executor executor, int transport) { 119 mCallback = callback; 120 mExecutor = executor; 121 mTransport = transport; 122 } 123 124 @Override onDeviceAdded(MidiDeviceInfo device)125 public void onDeviceAdded(MidiDeviceInfo device) { 126 if (shouldInvokeCallback(device)) { 127 if (mExecutor != null) { 128 mExecutor.execute(() -> 129 mCallback.onDeviceAdded(device)); 130 } else { 131 mCallback.onDeviceAdded(device); 132 } 133 } 134 } 135 136 @Override onDeviceRemoved(MidiDeviceInfo device)137 public void onDeviceRemoved(MidiDeviceInfo device) { 138 if (shouldInvokeCallback(device)) { 139 if (mExecutor != null) { 140 mExecutor.execute(() -> 141 mCallback.onDeviceRemoved(device)); 142 } else { 143 mCallback.onDeviceRemoved(device); 144 } 145 } 146 } 147 148 @Override onDeviceStatusChanged(MidiDeviceStatus status)149 public void onDeviceStatusChanged(MidiDeviceStatus status) { 150 if (mExecutor != null) { 151 mExecutor.execute(() -> 152 mCallback.onDeviceStatusChanged(status)); 153 } else { 154 mCallback.onDeviceStatusChanged(status); 155 } 156 } 157 158 /** 159 * Used to figure out whether callbacks should be invoked. Only invoke callbacks of 160 * the correct type. 161 * 162 * @param MidiDeviceInfo the device to check 163 * @return whether to invoke a callback 164 */ shouldInvokeCallback(MidiDeviceInfo device)165 private boolean shouldInvokeCallback(MidiDeviceInfo device) { 166 // UMP devices have protocols that are not PROTOCOL_UNKNOWN 167 if (mTransport == TRANSPORT_UNIVERSAL_MIDI_PACKETS) { 168 return (device.getDefaultProtocol() != MidiDeviceInfo.PROTOCOL_UNKNOWN); 169 } else if (mTransport == TRANSPORT_MIDI_BYTE_STREAM) { 170 return (device.getDefaultProtocol() == MidiDeviceInfo.PROTOCOL_UNKNOWN); 171 } else { 172 Log.e(TAG, "Invalid transport type: " + mTransport); 173 return false; 174 } 175 } 176 } 177 178 /** 179 * Callback class used for clients to receive MIDI device added and removed notifications 180 */ 181 public static class DeviceCallback { 182 /** 183 * Called to notify when a new MIDI device has been added 184 * 185 * @param device a {@link MidiDeviceInfo} for the newly added device 186 */ onDeviceAdded(MidiDeviceInfo device)187 public void onDeviceAdded(MidiDeviceInfo device) { 188 } 189 190 /** 191 * Called to notify when a MIDI device has been removed 192 * 193 * @param device a {@link MidiDeviceInfo} for the removed device 194 */ onDeviceRemoved(MidiDeviceInfo device)195 public void onDeviceRemoved(MidiDeviceInfo device) { 196 } 197 198 /** 199 * Called to notify when the status of a MIDI device has changed 200 * 201 * @param status a {@link MidiDeviceStatus} for the changed device 202 */ onDeviceStatusChanged(MidiDeviceStatus status)203 public void onDeviceStatusChanged(MidiDeviceStatus status) { 204 } 205 } 206 207 /** 208 * Listener class used for receiving the results of {@link #openDevice} and 209 * {@link #openBluetoothDevice} 210 */ 211 public interface OnDeviceOpenedListener { 212 /** 213 * Called to respond to a {@link #openDevice} request 214 * 215 * @param device a {@link MidiDevice} for opened device, or null if opening failed 216 */ onDeviceOpened(MidiDevice device)217 abstract public void onDeviceOpened(MidiDevice device); 218 } 219 220 /** 221 * @hide 222 */ MidiManager(IMidiManager service)223 public MidiManager(IMidiManager service) { 224 mService = service; 225 } 226 227 /** 228 * Registers a callback to receive notifications when MIDI 1.0 devices are added and removed. 229 * These are devices that do not default to Universal MIDI Packets. To register for a callback 230 * for those, call {@link #registerDeviceCallback} instead. 231 * 232 * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately 233 * for any devices that have open ports. This allows applications to know which input 234 * ports are already in use and, therefore, unavailable. 235 * 236 * Applications should call {@link #getDevices} before registering the callback 237 * to get a list of devices already added. 238 * 239 * @param callback a {@link DeviceCallback} for MIDI device notifications 240 * @param handler The {@link android.os.Handler Handler} that will be used for delivering the 241 * device notifications. If handler is null, then the thread used for the 242 * callback is unspecified. 243 * @deprecated Use {@link #registerDeviceCallback(int, Executor, DeviceCallback)} instead. 244 */ 245 @Deprecated registerDeviceCallback(DeviceCallback callback, Handler handler)246 public void registerDeviceCallback(DeviceCallback callback, Handler handler) { 247 Executor executor = null; 248 if (handler != null) { 249 executor = handler::post; 250 } 251 DeviceListener deviceListener = new DeviceListener(callback, executor, 252 TRANSPORT_MIDI_BYTE_STREAM); 253 try { 254 mService.registerListener(mToken, deviceListener); 255 } catch (RemoteException e) { 256 throw e.rethrowFromSystemServer(); 257 } 258 mDeviceListeners.put(callback, deviceListener); 259 } 260 261 /** 262 * Registers a callback to receive notifications when MIDI devices are added and removed 263 * for a specific transport type. 264 * 265 * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately 266 * for any devices that have open ports. This allows applications to know which input 267 * ports are already in use and, therefore, unavailable. 268 * 269 * Applications should call {@link #getDevicesForTransport} before registering the callback 270 * to get a list of devices already added. 271 * 272 * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or 273 * TRANSPORT_UNIVERSAL_MIDI_PACKETS. 274 * @param executor The {@link Executor} that will be used for delivering the 275 * device notifications. 276 * @param callback a {@link DeviceCallback} for MIDI device notifications 277 */ registerDeviceCallback(@ransport int transport, @NonNull Executor executor, @NonNull DeviceCallback callback)278 public void registerDeviceCallback(@Transport int transport, 279 @NonNull Executor executor, @NonNull DeviceCallback callback) { 280 Objects.requireNonNull(executor); 281 DeviceListener deviceListener = new DeviceListener(callback, executor, transport); 282 try { 283 mService.registerListener(mToken, deviceListener); 284 } catch (RemoteException e) { 285 throw e.rethrowFromSystemServer(); 286 } 287 mDeviceListeners.put(callback, deviceListener); 288 } 289 290 /** 291 * Unregisters a {@link DeviceCallback}. 292 * 293 * @param callback a {@link DeviceCallback} to unregister 294 */ unregisterDeviceCallback(DeviceCallback callback)295 public void unregisterDeviceCallback(DeviceCallback callback) { 296 DeviceListener deviceListener = mDeviceListeners.remove(callback); 297 if (deviceListener != null) { 298 try { 299 mService.unregisterListener(mToken, deviceListener); 300 } catch (RemoteException e) { 301 throw e.rethrowFromSystemServer(); 302 } 303 } 304 } 305 306 /** 307 * Gets a list of connected MIDI devices. This returns all devices that do 308 * not default to Universal MIDI Packets. To get those instead, please call 309 * {@link #getDevicesForTransport} instead. 310 * 311 * @return an array of MIDI devices 312 * @deprecated Use {@link #getDevicesForTransport} instead. 313 */ 314 @Deprecated getDevices()315 public MidiDeviceInfo[] getDevices() { 316 try { 317 return mService.getDevices(); 318 } catch (RemoteException e) { 319 throw e.rethrowFromSystemServer(); 320 } 321 } 322 323 /** 324 * Gets a list of connected MIDI devices by transport. TRANSPORT_MIDI_BYTE_STREAM 325 * is used for MIDI 1.0 and is the most common. 326 * For devices with built in Universal MIDI Packet support, use 327 * TRANSPORT_UNIVERSAL_MIDI_PACKETS instead. 328 * 329 * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or 330 * TRANSPORT_UNIVERSAL_MIDI_PACKETS. 331 * @return a collection of MIDI devices 332 */ getDevicesForTransport(@ransport int transport)333 public @NonNull Set<MidiDeviceInfo> getDevicesForTransport(@Transport int transport) { 334 try { 335 MidiDeviceInfo[] devices = mService.getDevicesForTransport(transport); 336 if (devices == null) { 337 return Collections.emptySet(); 338 } 339 return new ArraySet<>(devices); 340 } catch (RemoteException e) { 341 throw e.rethrowFromSystemServer(); 342 } 343 } 344 sendOpenDeviceResponse(final MidiDevice device, final OnDeviceOpenedListener listener, Handler handler)345 private void sendOpenDeviceResponse(final MidiDevice device, 346 final OnDeviceOpenedListener listener, Handler handler) { 347 if (handler != null) { 348 handler.post(new Runnable() { 349 @Override public void run() { 350 listener.onDeviceOpened(device); 351 } 352 }); 353 } else { 354 listener.onDeviceOpened(device); 355 } 356 } 357 358 /** 359 * Opens a MIDI device for reading and writing. 360 * 361 * @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open 362 * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called 363 * to receive the result 364 * @param handler the {@link android.os.Handler Handler} that will be used for delivering 365 * the result. If handler is null, then the thread used for the 366 * listener is unspecified. 367 */ openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener, Handler handler)368 public void openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener, 369 Handler handler) { 370 final MidiDeviceInfo deviceInfoF = deviceInfo; 371 final OnDeviceOpenedListener listenerF = listener; 372 final Handler handlerF = handler; 373 374 IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() { 375 @Override 376 public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) { 377 MidiDevice device; 378 if (server != null) { 379 device = new MidiDevice(deviceInfoF, server, mService, mToken, deviceToken); 380 } else { 381 device = null; 382 } 383 sendOpenDeviceResponse(device, listenerF, handlerF); 384 } 385 }; 386 387 try { 388 mService.openDevice(mToken, deviceInfo, callback); 389 } catch (RemoteException e) { 390 throw e.rethrowFromSystemServer(); 391 } 392 } 393 394 /** 395 * Opens a Bluetooth MIDI device for reading and writing. 396 * 397 * @param bluetoothDevice a {@link android.bluetooth.BluetoothDevice} to open as a MIDI device 398 * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called to receive the 399 * result 400 * @param handler the {@link android.os.Handler Handler} that will be used for delivering 401 * the result. If handler is null, then the thread used for the 402 * listener is unspecified. 403 */ openBluetoothDevice(BluetoothDevice bluetoothDevice, OnDeviceOpenedListener listener, Handler handler)404 public void openBluetoothDevice(BluetoothDevice bluetoothDevice, 405 OnDeviceOpenedListener listener, Handler handler) { 406 final OnDeviceOpenedListener listenerF = listener; 407 final Handler handlerF = handler; 408 409 Log.d(TAG, "openBluetoothDevice() " + bluetoothDevice); 410 IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() { 411 @Override 412 public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) { 413 Log.d(TAG, "onDeviceOpened() server:" + server); 414 MidiDevice device = null; 415 if (server != null) { 416 try { 417 // fetch MidiDeviceInfo from the server 418 MidiDeviceInfo deviceInfo = server.getDeviceInfo(); 419 device = new MidiDevice(deviceInfo, server, mService, mToken, deviceToken); 420 } catch (RemoteException e) { 421 Log.e(TAG, "remote exception in getDeviceInfo()"); 422 } 423 } 424 sendOpenDeviceResponse(device, listenerF, handlerF); 425 } 426 }; 427 428 try { 429 mService.openBluetoothDevice(mToken, bluetoothDevice, callback); 430 } catch (RemoteException e) { 431 throw e.rethrowFromSystemServer(); 432 } 433 } 434 435 /** @hide */ // for now closeBluetoothDevice(@onNull MidiDevice midiDevice)436 public void closeBluetoothDevice(@NonNull MidiDevice midiDevice) { 437 try { 438 midiDevice.close(); 439 } catch (IOException ex) { 440 Log.e(TAG, "Exception closing BLE-MIDI device" + ex); 441 } 442 } 443 444 /** @hide */ createDeviceServer(MidiReceiver[] inputPortReceivers, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, Bundle properties, int type, int defaultProtocol, MidiDeviceServer.Callback callback)445 public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers, 446 int numOutputPorts, String[] inputPortNames, String[] outputPortNames, 447 Bundle properties, int type, int defaultProtocol, 448 MidiDeviceServer.Callback callback) { 449 try { 450 MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers, 451 numOutputPorts, callback); 452 MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(), 453 inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames, 454 properties, type, defaultProtocol); 455 if (deviceInfo == null) { 456 Log.e(TAG, "registerVirtualDevice failed"); 457 return null; 458 } 459 return server; 460 } catch (RemoteException e) { 461 throw e.rethrowFromSystemServer(); 462 } 463 } 464 } 465