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.bluetooth.BluetoothDevice;
20 import android.os.Binder;
21 import android.os.IBinder;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.RemoteException;
25 import android.util.Log;
26 
27 import java.util.concurrent.ConcurrentHashMap;
28 
29 /**
30  * This class is the public application interface to the MIDI service.
31  *
32  * <p>You can obtain an instance of this class by calling
33  * {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
34  *
35  * {@samplecode
36  * MidiManager manager = (MidiManager) getSystemService(Context.MIDI_SERVICE);}
37  */
38 public final class MidiManager {
39     private static final String TAG = "MidiManager";
40 
41     /**
42      * Intent for starting BluetoothMidiService
43      * @hide
44      */
45     public static final String BLUETOOTH_MIDI_SERVICE_INTENT =
46                 "android.media.midi.BluetoothMidiService";
47 
48     /**
49      * BluetoothMidiService package name
50      * @hide
51      */
52     public static final String BLUETOOTH_MIDI_SERVICE_PACKAGE = "com.android.bluetoothmidiservice";
53 
54     /**
55      * BluetoothMidiService class name
56      * @hide
57      */
58     public static final String BLUETOOTH_MIDI_SERVICE_CLASS =
59                 "com.android.bluetoothmidiservice.BluetoothMidiService";
60 
61     private final IMidiManager mService;
62     private final IBinder mToken = new Binder();
63 
64     private ConcurrentHashMap<DeviceCallback,DeviceListener> mDeviceListeners =
65         new ConcurrentHashMap<DeviceCallback,DeviceListener>();
66 
67     // Binder stub for receiving device notifications from MidiService
68     private class DeviceListener extends IMidiDeviceListener.Stub {
69         private final DeviceCallback mCallback;
70         private final Handler mHandler;
71 
DeviceListener(DeviceCallback callback, Handler handler)72         public DeviceListener(DeviceCallback callback, Handler handler) {
73             mCallback = callback;
74             mHandler = handler;
75         }
76 
77         @Override
onDeviceAdded(MidiDeviceInfo device)78         public void onDeviceAdded(MidiDeviceInfo device) {
79             if (mHandler != null) {
80                 final MidiDeviceInfo deviceF = device;
81                 mHandler.post(new Runnable() {
82                         @Override public void run() {
83                             mCallback.onDeviceAdded(deviceF);
84                         }
85                     });
86             } else {
87                 mCallback.onDeviceAdded(device);
88             }
89         }
90 
91         @Override
onDeviceRemoved(MidiDeviceInfo device)92         public void onDeviceRemoved(MidiDeviceInfo device) {
93             if (mHandler != null) {
94                 final MidiDeviceInfo deviceF = device;
95                 mHandler.post(new Runnable() {
96                         @Override public void run() {
97                             mCallback.onDeviceRemoved(deviceF);
98                         }
99                     });
100             } else {
101                 mCallback.onDeviceRemoved(device);
102             }
103         }
104 
105         @Override
onDeviceStatusChanged(MidiDeviceStatus status)106         public void onDeviceStatusChanged(MidiDeviceStatus status) {
107             if (mHandler != null) {
108                 final MidiDeviceStatus statusF = status;
109                 mHandler.post(new Runnable() {
110                         @Override public void run() {
111                             mCallback.onDeviceStatusChanged(statusF);
112                         }
113                     });
114             } else {
115                 mCallback.onDeviceStatusChanged(status);
116             }
117         }
118     }
119 
120     /**
121      * Callback class used for clients to receive MIDI device added and removed notifications
122      */
123     public static class DeviceCallback {
124         /**
125          * Called to notify when a new MIDI device has been added
126          *
127          * @param device a {@link MidiDeviceInfo} for the newly added device
128          */
onDeviceAdded(MidiDeviceInfo device)129         public void onDeviceAdded(MidiDeviceInfo device) {
130         }
131 
132         /**
133          * Called to notify when a MIDI device has been removed
134          *
135          * @param device a {@link MidiDeviceInfo} for the removed device
136          */
onDeviceRemoved(MidiDeviceInfo device)137         public void onDeviceRemoved(MidiDeviceInfo device) {
138         }
139 
140         /**
141          * Called to notify when the status of a MIDI device has changed
142          *
143          * @param status a {@link MidiDeviceStatus} for the changed device
144          */
onDeviceStatusChanged(MidiDeviceStatus status)145         public void onDeviceStatusChanged(MidiDeviceStatus status) {
146         }
147     }
148 
149     /**
150      * Listener class used for receiving the results of {@link #openDevice} and
151      * {@link #openBluetoothDevice}
152      */
153     public interface OnDeviceOpenedListener {
154         /**
155          * Called to respond to a {@link #openDevice} request
156          *
157          * @param device a {@link MidiDevice} for opened device, or null if opening failed
158          */
onDeviceOpened(MidiDevice device)159         abstract public void onDeviceOpened(MidiDevice device);
160     }
161 
162     /**
163      * @hide
164      */
MidiManager(IMidiManager service)165     public MidiManager(IMidiManager service) {
166         mService = service;
167     }
168 
169     /**
170      * Registers a callback to receive notifications when MIDI devices are added and removed.
171      *
172      * @param callback a {@link DeviceCallback} for MIDI device notifications
173      * @param handler The {@link android.os.Handler Handler} that will be used for delivering the
174      *                device notifications. If handler is null, then the thread used for the
175      *                callback is unspecified.
176      */
registerDeviceCallback(DeviceCallback callback, Handler handler)177     public void registerDeviceCallback(DeviceCallback callback, Handler handler) {
178         DeviceListener deviceListener = new DeviceListener(callback, handler);
179         try {
180             mService.registerListener(mToken, deviceListener);
181         } catch (RemoteException e) {
182             Log.e(TAG, "RemoteException in registerDeviceListener");
183             return;
184         }
185         mDeviceListeners.put(callback, deviceListener);
186     }
187 
188     /**
189      * Unregisters a {@link DeviceCallback}.
190       *
191      * @param callback a {@link DeviceCallback} to unregister
192      */
unregisterDeviceCallback(DeviceCallback callback)193     public void unregisterDeviceCallback(DeviceCallback callback) {
194         DeviceListener deviceListener = mDeviceListeners.remove(callback);
195         if (deviceListener != null) {
196             try {
197                 mService.unregisterListener(mToken, deviceListener);
198             } catch (RemoteException e) {
199                 Log.e(TAG, "RemoteException in unregisterDeviceListener");
200             }
201         }
202     }
203 
204     /**
205      * Gets the list of all connected MIDI devices.
206      *
207      * @return an array of all MIDI devices
208      */
getDevices()209     public MidiDeviceInfo[] getDevices() {
210         try {
211            return mService.getDevices();
212         } catch (RemoteException e) {
213             Log.e(TAG, "RemoteException in getDevices");
214             return new MidiDeviceInfo[0];
215         }
216     }
217 
sendOpenDeviceResponse(final MidiDevice device, final OnDeviceOpenedListener listener, Handler handler)218     private void sendOpenDeviceResponse(final MidiDevice device,
219             final OnDeviceOpenedListener listener, Handler handler) {
220         if (handler != null) {
221             handler.post(new Runnable() {
222                     @Override public void run() {
223                         listener.onDeviceOpened(device);
224                     }
225                 });
226         } else {
227             listener.onDeviceOpened(device);
228         }
229     }
230 
231     /**
232      * Opens a MIDI device for reading and writing.
233      *
234      * @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open
235      * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called
236      *                 to receive the result
237      * @param handler the {@link android.os.Handler Handler} that will be used for delivering
238      *                the result. If handler is null, then the thread used for the
239      *                listener is unspecified.
240      */
openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener, Handler handler)241     public void openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener,
242             Handler handler) {
243         final MidiDeviceInfo deviceInfoF = deviceInfo;
244         final OnDeviceOpenedListener listenerF = listener;
245         final Handler handlerF = handler;
246 
247         IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() {
248             @Override
249             public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) {
250                 MidiDevice device;
251                 if (server != null) {
252                     device = new MidiDevice(deviceInfoF, server, mService, mToken, deviceToken);
253                 } else {
254                     device = null;
255                 }
256                 sendOpenDeviceResponse(device, listenerF, handlerF);
257             }
258         };
259 
260         try {
261             mService.openDevice(mToken, deviceInfo, callback);
262         } catch (RemoteException e) {
263             Log.e(TAG, "RemoteException in openDevice");
264         }
265     }
266 
267     /**
268      * Opens a Bluetooth MIDI device for reading and writing.
269      *
270      * @param bluetoothDevice a {@link android.bluetooth.BluetoothDevice} to open as a MIDI device
271      * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called to receive the
272      * result
273      * @param handler the {@link android.os.Handler Handler} that will be used for delivering
274      *                the result. If handler is null, then the thread used for the
275      *                listener is unspecified.
276      */
openBluetoothDevice(BluetoothDevice bluetoothDevice, OnDeviceOpenedListener listener, Handler handler)277     public void openBluetoothDevice(BluetoothDevice bluetoothDevice,
278             OnDeviceOpenedListener listener, Handler handler) {
279         final OnDeviceOpenedListener listenerF = listener;
280         final Handler handlerF = handler;
281 
282         IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() {
283             @Override
284             public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) {
285                 MidiDevice device = null;
286                 if (server != null) {
287                     try {
288                         // fetch MidiDeviceInfo from the server
289                         MidiDeviceInfo deviceInfo = server.getDeviceInfo();
290                         device = new MidiDevice(deviceInfo, server, mService, mToken, deviceToken);
291                         sendOpenDeviceResponse(device, listenerF, handlerF);
292                     } catch (RemoteException e) {
293                         Log.e(TAG, "remote exception in getDeviceInfo()");
294                     }
295                 }
296                 sendOpenDeviceResponse(device, listenerF, handlerF);
297             }
298         };
299 
300         try {
301             mService.openBluetoothDevice(mToken, bluetoothDevice, callback);
302         } catch (RemoteException e) {
303             Log.e(TAG, "RemoteException in openDevice");
304         }
305     }
306 
307     /** @hide */
createDeviceServer(MidiReceiver[] inputPortReceivers, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, Bundle properties, int type, MidiDeviceServer.Callback callback)308     public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
309             int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
310             Bundle properties, int type, MidiDeviceServer.Callback callback) {
311         try {
312             MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
313                     numOutputPorts, callback);
314             MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
315                     inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames,
316                     properties, type);
317             if (deviceInfo == null) {
318                 Log.e(TAG, "registerVirtualDevice failed");
319                 return null;
320             }
321             return server;
322         } catch (RemoteException e) {
323             Log.e(TAG, "RemoteException in createVirtualDevice");
324             return null;
325         }
326     }
327 }
328