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.RequiresFeature;
20 import android.annotation.SystemService;
21 import android.bluetooth.BluetoothDevice;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.os.Binder;
25 import android.os.IBinder;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.RemoteException;
29 import android.util.Log;
30 
31 import java.util.concurrent.ConcurrentHashMap;
32 
33 /**
34  * This class is the public application interface to the MIDI service.
35  */
36 @SystemService(Context.MIDI_SERVICE)
37 @RequiresFeature(PackageManager.FEATURE_MIDI)
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      * The {@link  DeviceCallback#onDeviceStatusChanged} method will be called immediately
173      * for any devices that have open ports. This allows applications to know which input
174      * ports are already in use and, therefore, unavailable.
175      *
176      * Applications should call {@link #getDevices} before registering the callback
177      * to get a list of devices already added.
178      *
179      * @param callback a {@link DeviceCallback} for MIDI device notifications
180      * @param handler The {@link android.os.Handler Handler} that will be used for delivering the
181      *                device notifications. If handler is null, then the thread used for the
182      *                callback is unspecified.
183      */
registerDeviceCallback(DeviceCallback callback, Handler handler)184     public void registerDeviceCallback(DeviceCallback callback, Handler handler) {
185         DeviceListener deviceListener = new DeviceListener(callback, handler);
186         try {
187             mService.registerListener(mToken, deviceListener);
188         } catch (RemoteException e) {
189             throw e.rethrowFromSystemServer();
190         }
191         mDeviceListeners.put(callback, deviceListener);
192     }
193 
194     /**
195      * Unregisters a {@link DeviceCallback}.
196       *
197      * @param callback a {@link DeviceCallback} to unregister
198      */
unregisterDeviceCallback(DeviceCallback callback)199     public void unregisterDeviceCallback(DeviceCallback callback) {
200         DeviceListener deviceListener = mDeviceListeners.remove(callback);
201         if (deviceListener != null) {
202             try {
203                 mService.unregisterListener(mToken, deviceListener);
204             } catch (RemoteException e) {
205                 throw e.rethrowFromSystemServer();
206             }
207         }
208     }
209 
210     /**
211      * Gets the list of all connected MIDI devices.
212      *
213      * @return an array of all MIDI devices
214      */
getDevices()215     public MidiDeviceInfo[] getDevices() {
216         try {
217            return mService.getDevices();
218         } catch (RemoteException e) {
219             throw e.rethrowFromSystemServer();
220         }
221     }
222 
sendOpenDeviceResponse(final MidiDevice device, final OnDeviceOpenedListener listener, Handler handler)223     private void sendOpenDeviceResponse(final MidiDevice device,
224             final OnDeviceOpenedListener listener, Handler handler) {
225         if (handler != null) {
226             handler.post(new Runnable() {
227                     @Override public void run() {
228                         listener.onDeviceOpened(device);
229                     }
230                 });
231         } else {
232             listener.onDeviceOpened(device);
233         }
234     }
235 
236     /**
237      * Opens a MIDI device for reading and writing.
238      *
239      * @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open
240      * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called
241      *                 to receive the result
242      * @param handler the {@link android.os.Handler Handler} that will be used for delivering
243      *                the result. If handler is null, then the thread used for the
244      *                listener is unspecified.
245      */
openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener, Handler handler)246     public void openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener,
247             Handler handler) {
248         final MidiDeviceInfo deviceInfoF = deviceInfo;
249         final OnDeviceOpenedListener listenerF = listener;
250         final Handler handlerF = handler;
251 
252         IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() {
253             @Override
254             public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) {
255                 MidiDevice device;
256                 if (server != null) {
257                     device = new MidiDevice(deviceInfoF, server, mService, mToken, deviceToken);
258                 } else {
259                     device = null;
260                 }
261                 sendOpenDeviceResponse(device, listenerF, handlerF);
262             }
263         };
264 
265         try {
266             mService.openDevice(mToken, deviceInfo, callback);
267         } catch (RemoteException e) {
268             throw e.rethrowFromSystemServer();
269         }
270     }
271 
272     /**
273      * Opens a Bluetooth MIDI device for reading and writing.
274      *
275      * @param bluetoothDevice a {@link android.bluetooth.BluetoothDevice} to open as a MIDI device
276      * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called to receive the
277      * result
278      * @param handler the {@link android.os.Handler Handler} that will be used for delivering
279      *                the result. If handler is null, then the thread used for the
280      *                listener is unspecified.
281      */
openBluetoothDevice(BluetoothDevice bluetoothDevice, OnDeviceOpenedListener listener, Handler handler)282     public void openBluetoothDevice(BluetoothDevice bluetoothDevice,
283             OnDeviceOpenedListener listener, Handler handler) {
284         final OnDeviceOpenedListener listenerF = listener;
285         final Handler handlerF = handler;
286 
287         IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() {
288             @Override
289             public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) {
290                 MidiDevice device = null;
291                 if (server != null) {
292                     try {
293                         // fetch MidiDeviceInfo from the server
294                         MidiDeviceInfo deviceInfo = server.getDeviceInfo();
295                         device = new MidiDevice(deviceInfo, server, mService, mToken, deviceToken);
296                     } catch (RemoteException e) {
297                         Log.e(TAG, "remote exception in getDeviceInfo()");
298                     }
299                 }
300                 sendOpenDeviceResponse(device, listenerF, handlerF);
301             }
302         };
303 
304         try {
305             mService.openBluetoothDevice(mToken, bluetoothDevice, callback);
306         } catch (RemoteException e) {
307             throw e.rethrowFromSystemServer();
308         }
309     }
310 
311     /** @hide */
createDeviceServer(MidiReceiver[] inputPortReceivers, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, Bundle properties, int type, MidiDeviceServer.Callback callback)312     public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
313             int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
314             Bundle properties, int type, MidiDeviceServer.Callback callback) {
315         try {
316             MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
317                     numOutputPorts, callback);
318             MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
319                     inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames,
320                     properties, type);
321             if (deviceInfo == null) {
322                 Log.e(TAG, "registerVirtualDevice failed");
323                 return null;
324             }
325             return server;
326         } catch (RemoteException e) {
327             throw e.rethrowFromSystemServer();
328         }
329     }
330 }
331