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
17package android.media.midi;
18
19import android.bluetooth.BluetoothDevice;
20import android.os.Binder;
21import android.os.IBinder;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.RemoteException;
25import android.util.Log;
26
27import 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 */
38public 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
72        public DeviceListener(DeviceCallback callback, Handler handler) {
73            mCallback = callback;
74            mHandler = handler;
75        }
76
77        @Override
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
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
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         */
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         */
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         */
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         */
159        abstract public void onDeviceOpened(MidiDevice device);
160    }
161
162    /**
163     * @hide
164     */
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     */
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            Log.e(TAG, "RemoteException in registerDeviceListener");
190            return;
191        }
192        mDeviceListeners.put(callback, deviceListener);
193    }
194
195    /**
196     * Unregisters a {@link DeviceCallback}.
197      *
198     * @param callback a {@link DeviceCallback} to unregister
199     */
200    public void unregisterDeviceCallback(DeviceCallback callback) {
201        DeviceListener deviceListener = mDeviceListeners.remove(callback);
202        if (deviceListener != null) {
203            try {
204                mService.unregisterListener(mToken, deviceListener);
205            } catch (RemoteException e) {
206                Log.e(TAG, "RemoteException in unregisterDeviceListener");
207            }
208        }
209    }
210
211    /**
212     * Gets the list of all connected MIDI devices.
213     *
214     * @return an array of all MIDI devices
215     */
216    public MidiDeviceInfo[] getDevices() {
217        try {
218           return mService.getDevices();
219        } catch (RemoteException e) {
220            Log.e(TAG, "RemoteException in getDevices");
221            return new MidiDeviceInfo[0];
222        }
223    }
224
225    private void sendOpenDeviceResponse(final MidiDevice device,
226            final OnDeviceOpenedListener listener, Handler handler) {
227        if (handler != null) {
228            handler.post(new Runnable() {
229                    @Override public void run() {
230                        listener.onDeviceOpened(device);
231                    }
232                });
233        } else {
234            listener.onDeviceOpened(device);
235        }
236    }
237
238    /**
239     * Opens a MIDI device for reading and writing.
240     *
241     * @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open
242     * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called
243     *                 to receive the result
244     * @param handler the {@link android.os.Handler Handler} that will be used for delivering
245     *                the result. If handler is null, then the thread used for the
246     *                listener is unspecified.
247     */
248    public void openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener,
249            Handler handler) {
250        final MidiDeviceInfo deviceInfoF = deviceInfo;
251        final OnDeviceOpenedListener listenerF = listener;
252        final Handler handlerF = handler;
253
254        IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() {
255            @Override
256            public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) {
257                MidiDevice device;
258                if (server != null) {
259                    device = new MidiDevice(deviceInfoF, server, mService, mToken, deviceToken);
260                } else {
261                    device = null;
262                }
263                sendOpenDeviceResponse(device, listenerF, handlerF);
264            }
265        };
266
267        try {
268            mService.openDevice(mToken, deviceInfo, callback);
269        } catch (RemoteException e) {
270            Log.e(TAG, "RemoteException in openDevice");
271        }
272    }
273
274    /**
275     * Opens a Bluetooth MIDI device for reading and writing.
276     *
277     * @param bluetoothDevice a {@link android.bluetooth.BluetoothDevice} to open as a MIDI device
278     * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called to receive the
279     * result
280     * @param handler the {@link android.os.Handler Handler} that will be used for delivering
281     *                the result. If handler is null, then the thread used for the
282     *                listener is unspecified.
283     */
284    public void openBluetoothDevice(BluetoothDevice bluetoothDevice,
285            OnDeviceOpenedListener listener, Handler handler) {
286        final OnDeviceOpenedListener listenerF = listener;
287        final Handler handlerF = handler;
288
289        IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() {
290            @Override
291            public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) {
292                MidiDevice device = null;
293                if (server != null) {
294                    try {
295                        // fetch MidiDeviceInfo from the server
296                        MidiDeviceInfo deviceInfo = server.getDeviceInfo();
297                        device = new MidiDevice(deviceInfo, server, mService, mToken, deviceToken);
298                    } catch (RemoteException e) {
299                        Log.e(TAG, "remote exception in getDeviceInfo()");
300                    }
301                }
302                sendOpenDeviceResponse(device, listenerF, handlerF);
303            }
304        };
305
306        try {
307            mService.openBluetoothDevice(mToken, bluetoothDevice, callback);
308        } catch (RemoteException e) {
309            Log.e(TAG, "RemoteException in openDevice");
310        }
311    }
312
313    /** @hide */
314    public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
315            int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
316            Bundle properties, int type, MidiDeviceServer.Callback callback) {
317        try {
318            MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
319                    numOutputPorts, callback);
320            MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
321                    inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames,
322                    properties, type);
323            if (deviceInfo == null) {
324                Log.e(TAG, "registerVirtualDevice failed");
325                return null;
326            }
327            return server;
328        } catch (RemoteException e) {
329            Log.e(TAG, "RemoteException in createVirtualDevice");
330            return null;
331        }
332    }
333}
334