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