• 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 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