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