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            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     */
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     */
215    public MidiDeviceInfo[] getDevices() {
216        try {
217           return mService.getDevices();
218        } catch (RemoteException e) {
219            throw e.rethrowFromSystemServer();
220        }
221    }
222
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     */
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     */
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 */
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