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 the {@link #registerDeviceCallback} 244 * method with Executor and transport instead. 245 */ 246 @Deprecated registerDeviceCallback(DeviceCallback callback, Handler handler)247 public void registerDeviceCallback(DeviceCallback callback, Handler handler) { 248 Executor executor = null; 249 if (handler != null) { 250 executor = handler::post; 251 } 252 DeviceListener deviceListener = new DeviceListener(callback, executor, 253 TRANSPORT_MIDI_BYTE_STREAM); 254 try { 255 mService.registerListener(mToken, deviceListener); 256 } catch (RemoteException e) { 257 throw e.rethrowFromSystemServer(); 258 } 259 mDeviceListeners.put(callback, deviceListener); 260 } 261 262 /** 263 * Registers a callback to receive notifications when MIDI devices are added and removed 264 * for a specific transport type. 265 * 266 * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately 267 * for any devices that have open ports. This allows applications to know which input 268 * ports are already in use and, therefore, unavailable. 269 * 270 * Applications should call {@link #getDevicesForTransport} before registering the callback 271 * to get a list of devices already added. 272 * 273 * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or 274 * TRANSPORT_UNIVERSAL_MIDI_PACKETS. 275 * @param executor The {@link Executor} that will be used for delivering the 276 * device notifications. 277 * @param callback a {@link DeviceCallback} for MIDI device notifications 278 */ registerDeviceCallback(@ransport int transport, @NonNull Executor executor, @NonNull DeviceCallback callback)279 public void registerDeviceCallback(@Transport int transport, 280 @NonNull Executor executor, @NonNull DeviceCallback callback) { 281 Objects.requireNonNull(executor); 282 DeviceListener deviceListener = new DeviceListener(callback, executor, transport); 283 try { 284 mService.registerListener(mToken, deviceListener); 285 } catch (RemoteException e) { 286 throw e.rethrowFromSystemServer(); 287 } 288 mDeviceListeners.put(callback, deviceListener); 289 } 290 291 /** 292 * Unregisters a {@link DeviceCallback}. 293 * 294 * @param callback a {@link DeviceCallback} to unregister 295 */ unregisterDeviceCallback(DeviceCallback callback)296 public void unregisterDeviceCallback(DeviceCallback callback) { 297 DeviceListener deviceListener = mDeviceListeners.remove(callback); 298 if (deviceListener != null) { 299 try { 300 mService.unregisterListener(mToken, deviceListener); 301 } catch (RemoteException e) { 302 throw e.rethrowFromSystemServer(); 303 } 304 } 305 } 306 307 /** 308 * Gets a list of connected MIDI devices. This returns all devices that do 309 * not default to Universal MIDI Packets. To get those instead, please call 310 * {@link #getDevicesForTransport} instead. 311 * 312 * @return an array of MIDI devices 313 * @deprecated Use {@link #getDevicesForTransport} instead. 314 */ 315 @Deprecated getDevices()316 public MidiDeviceInfo[] getDevices() { 317 try { 318 return mService.getDevices(); 319 } catch (RemoteException e) { 320 throw e.rethrowFromSystemServer(); 321 } 322 } 323 324 /** 325 * Gets a list of connected MIDI devices by transport. TRANSPORT_MIDI_BYTE_STREAM 326 * is used for MIDI 1.0 and is the most common. 327 * For devices with built in Universal MIDI Packet support, use 328 * TRANSPORT_UNIVERSAL_MIDI_PACKETS instead. 329 * 330 * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or 331 * TRANSPORT_UNIVERSAL_MIDI_PACKETS. 332 * @return a collection of MIDI devices 333 */ getDevicesForTransport(@ransport int transport)334 public @NonNull Set<MidiDeviceInfo> getDevicesForTransport(@Transport int transport) { 335 try { 336 MidiDeviceInfo[] devices = mService.getDevicesForTransport(transport); 337 if (devices == null) { 338 return Collections.emptySet(); 339 } 340 return new ArraySet<>(devices); 341 } catch (RemoteException e) { 342 throw e.rethrowFromSystemServer(); 343 } 344 } 345 sendOpenDeviceResponse(final MidiDevice device, final OnDeviceOpenedListener listener, Handler handler)346 private void sendOpenDeviceResponse(final MidiDevice device, 347 final OnDeviceOpenedListener listener, Handler handler) { 348 if (handler != null) { 349 handler.post(new Runnable() { 350 @Override public void run() { 351 listener.onDeviceOpened(device); 352 } 353 }); 354 } else { 355 listener.onDeviceOpened(device); 356 } 357 } 358 359 /** 360 * Opens a MIDI device for reading and writing. 361 * 362 * @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open 363 * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called 364 * to receive the result 365 * @param handler the {@link android.os.Handler Handler} that will be used for delivering 366 * the result. If handler is null, then the thread used for the 367 * listener is unspecified. 368 */ openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener, Handler handler)369 public void openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener, 370 Handler handler) { 371 final MidiDeviceInfo deviceInfoF = deviceInfo; 372 final OnDeviceOpenedListener listenerF = listener; 373 final Handler handlerF = handler; 374 375 IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() { 376 @Override 377 public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) { 378 MidiDevice device; 379 if (server != null) { 380 device = new MidiDevice(deviceInfoF, server, mService, mToken, deviceToken); 381 } else { 382 device = null; 383 } 384 sendOpenDeviceResponse(device, listenerF, handlerF); 385 } 386 }; 387 388 try { 389 mService.openDevice(mToken, deviceInfo, callback); 390 } catch (RemoteException e) { 391 throw e.rethrowFromSystemServer(); 392 } 393 } 394 395 /** 396 * Opens a Bluetooth MIDI device for reading and writing. 397 * 398 * @param bluetoothDevice a {@link android.bluetooth.BluetoothDevice} to open as a MIDI device 399 * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called to receive the 400 * result 401 * @param handler the {@link android.os.Handler Handler} that will be used for delivering 402 * the result. If handler is null, then the thread used for the 403 * listener is unspecified. 404 */ openBluetoothDevice(BluetoothDevice bluetoothDevice, OnDeviceOpenedListener listener, Handler handler)405 public void openBluetoothDevice(BluetoothDevice bluetoothDevice, 406 OnDeviceOpenedListener listener, Handler handler) { 407 final OnDeviceOpenedListener listenerF = listener; 408 final Handler handlerF = handler; 409 410 Log.d(TAG, "openBluetoothDevice() " + bluetoothDevice); 411 IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() { 412 @Override 413 public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) { 414 Log.d(TAG, "onDeviceOpened() server:" + server); 415 MidiDevice device = null; 416 if (server != null) { 417 try { 418 // fetch MidiDeviceInfo from the server 419 MidiDeviceInfo deviceInfo = server.getDeviceInfo(); 420 device = new MidiDevice(deviceInfo, server, mService, mToken, deviceToken); 421 } catch (RemoteException e) { 422 Log.e(TAG, "remote exception in getDeviceInfo()"); 423 } 424 } 425 sendOpenDeviceResponse(device, listenerF, handlerF); 426 } 427 }; 428 429 try { 430 mService.openBluetoothDevice(mToken, bluetoothDevice, callback); 431 } catch (RemoteException e) { 432 throw e.rethrowFromSystemServer(); 433 } 434 } 435 436 /** @hide */ // for now closeBluetoothDevice(@onNull MidiDevice midiDevice)437 public void closeBluetoothDevice(@NonNull MidiDevice midiDevice) { 438 try { 439 midiDevice.close(); 440 } catch (IOException ex) { 441 Log.e(TAG, "Exception closing BLE-MIDI device" + ex); 442 } 443 } 444 445 /** @hide */ createDeviceServer(MidiReceiver[] inputPortReceivers, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, Bundle properties, int type, int defaultProtocol, MidiDeviceServer.Callback callback)446 public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers, 447 int numOutputPorts, String[] inputPortNames, String[] outputPortNames, 448 Bundle properties, int type, int defaultProtocol, 449 MidiDeviceServer.Callback callback) { 450 try { 451 MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers, 452 numOutputPorts, callback); 453 MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(), 454 inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames, 455 properties, type, defaultProtocol); 456 if (deviceInfo == null) { 457 Log.e(TAG, "registerVirtualDevice failed"); 458 return null; 459 } 460 return server; 461 } catch (RemoteException e) { 462 throw e.rethrowFromSystemServer(); 463 } 464 } 465 } 466