• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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