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