content: /*
   * Copyright (C) 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.media.midi;
  
  import android.annotation.RequiresFeature;
  import android.annotation.SystemService;
  import android.bluetooth.BluetoothDevice;
  import android.content.Context;
  import android.content.pm.PackageManager;
  import android.os.Binder;
  import android.os.IBinder;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.RemoteException;
  import android.util.Log;
  
  import java.util.concurrent.ConcurrentHashMap;
  
  /**
   * This class is the public application interface to the MIDI service.
   */
  @SystemService(Context.MIDI_SERVICE)
  @RequiresFeature(PackageManager.FEATURE_MIDI)
  public final class MidiManager {
      private static final String TAG = "MidiManager";
  
      /**
       * Intent for starting BluetoothMidiService
       * @hide
       */
      public static final String BLUETOOTH_MIDI_SERVICE_INTENT =
                  "android.media.midi.BluetoothMidiService";
  
      /**
       * BluetoothMidiService package name
       * @hide
       */
      public static final String BLUETOOTH_MIDI_SERVICE_PACKAGE = "com.android.bluetoothmidiservice";
  
      /**
       * BluetoothMidiService class name
       * @hide
       */
      public static final String BLUETOOTH_MIDI_SERVICE_CLASS =
                  "com.android.bluetoothmidiservice.BluetoothMidiService";
  
      private final IMidiManager mService;
      private final IBinder mToken = new Binder();
  
      private ConcurrentHashMap<DeviceCallback,DeviceListener> mDeviceListeners =
          new ConcurrentHashMap<DeviceCallback,DeviceListener>();
  
      // Binder stub for receiving device notifications from MidiService
      private class DeviceListener extends IMidiDeviceListener.Stub {
          private final DeviceCallback mCallback;
          private final Handler mHandler;
  
          public DeviceListener(DeviceCallback callback, Handler handler) {
              mCallback = callback;
              mHandler = handler;
          }
  
          @Override
          public void onDeviceAdded(MidiDeviceInfo device) {
              if (mHandler != null) {
                  final MidiDeviceInfo deviceF = device;
                  mHandler.post(new Runnable() {
                          @Override public void run() {
                              mCallback.onDeviceAdded(deviceF);
                          }
                      });
              } else {
                  mCallback.onDeviceAdded(device);
              }
          }
  
          @Override
          public void onDeviceRemoved(MidiDeviceInfo device) {
              if (mHandler != null) {
                  final MidiDeviceInfo deviceF = device;
                  mHandler.post(new Runnable() {
                          @Override public void run() {
                              mCallback.onDeviceRemoved(deviceF);
                          }
                      });
              } else {
                  mCallback.onDeviceRemoved(device);
              }
          }
  
          @Override
          public void onDeviceStatusChanged(MidiDeviceStatus status) {
              if (mHandler != null) {
                  final MidiDeviceStatus statusF = status;
                  mHandler.post(new Runnable() {
                          @Override public void run() {
                              mCallback.onDeviceStatusChanged(statusF);
                          }
                      });
              } else {
                  mCallback.onDeviceStatusChanged(status);
              }
          }
      }
  
      /**
       * Callback class used for clients to receive MIDI device added and removed notifications
       */
      public static class DeviceCallback {
          /**
           * Called to notify when a new MIDI device has been added
           *
           * @param device a {@link MidiDeviceInfo} for the newly added device
           */
          public void onDeviceAdded(MidiDeviceInfo device) {
          }
  
          /**
           * Called to notify when a MIDI device has been removed
           *
           * @param device a {@link MidiDeviceInfo} for the removed device
           */
          public void onDeviceRemoved(MidiDeviceInfo device) {
          }
  
          /**
           * Called to notify when the status of a MIDI device has changed
           *
           * @param status a {@link MidiDeviceStatus} for the changed device
           */
          public void onDeviceStatusChanged(MidiDeviceStatus status) {
          }
      }
  
      /**
       * Listener class used for receiving the results of {@link #openDevice} and
       * {@link #openBluetoothDevice}
       */
      public interface OnDeviceOpenedListener {
          /**
           * Called to respond to a {@link #openDevice} request
           *
           * @param device a {@link MidiDevice} for opened device, or null if opening failed
           */
          abstract public void onDeviceOpened(MidiDevice device);
      }
  
      /**
       * @hide
       */
      public MidiManager(IMidiManager service) {
          mService = service;
      }
  
      /**
       * Registers a callback to receive notifications when MIDI devices are added and removed.
       *
       * The {@link  DeviceCallback#onDeviceStatusChanged} method will be called immediately
       * for any devices that have open ports. This allows applications to know which input
       * ports are already in use and, therefore, unavailable.
       *
       * Applications should call {@link #getDevices} before registering the callback
       * to get a list of devices already added.
       *
       * @param callback a {@link DeviceCallback} for MIDI device notifications
       * @param handler The {@link android.os.Handler Handler} that will be used for delivering the
       *                device notifications. If handler is null, then the thread used for the
       *                callback is unspecified.
       */
      public void registerDeviceCallback(DeviceCallback callback, Handler handler) {
          DeviceListener deviceListener = new DeviceListener(callback, handler);
          try {
              mService.registerListener(mToken, deviceListener);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
          mDeviceListeners.put(callback, deviceListener);
      }
  
      /**
       * Unregisters a {@link DeviceCallback}.
        *
       * @param callback a {@link DeviceCallback} to unregister
       */
      public void unregisterDeviceCallback(DeviceCallback callback) {
          DeviceListener deviceListener = mDeviceListeners.remove(callback);
          if (deviceListener != null) {
              try {
                  mService.unregisterListener(mToken, deviceListener);
              } catch (RemoteException e) {
                  throw e.rethrowFromSystemServer();
              }
          }
      }
  
      /**
       * Gets the list of all connected MIDI devices.
       *
       * @return an array of all MIDI devices
       */
      public MidiDeviceInfo[] getDevices() {
          try {
             return mService.getDevices();
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      private void sendOpenDeviceResponse(final MidiDevice device,
              final OnDeviceOpenedListener listener, Handler handler) {
          if (handler != null) {
              handler.post(new Runnable() {
                      @Override public void run() {
                          listener.onDeviceOpened(device);
                      }
                  });
          } else {
              listener.onDeviceOpened(device);
          }
      }
  
      /**
       * Opens a MIDI device for reading and writing.
       *
       * @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open
       * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called
       *                 to receive the result
       * @param handler the {@link android.os.Handler Handler} that will be used for delivering
       *                the result. If handler is null, then the thread used for the
       *                listener is unspecified.
       */
      public void openDevice(MidiDeviceInfo deviceInfo, OnDeviceOpenedListener listener,
              Handler handler) {
          final MidiDeviceInfo deviceInfoF = deviceInfo;
          final OnDeviceOpenedListener listenerF = listener;
          final Handler handlerF = handler;
  
          IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() {
              @Override
              public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) {
                  MidiDevice device;
                  if (server != null) {
                      device = new MidiDevice(deviceInfoF, server, mService, mToken, deviceToken);
                  } else {
                      device = null;
                  }
                  sendOpenDeviceResponse(device, listenerF, handlerF);
              }
          };
  
          try {
              mService.openDevice(mToken, deviceInfo, callback);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /**
       * Opens a Bluetooth MIDI device for reading and writing.
       *
       * @param bluetoothDevice a {@link android.bluetooth.BluetoothDevice} to open as a MIDI device
       * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called to receive the
       * result
       * @param handler the {@link android.os.Handler Handler} that will be used for delivering
       *                the result. If handler is null, then the thread used for the
       *                listener is unspecified.
       */
      public void openBluetoothDevice(BluetoothDevice bluetoothDevice,
              OnDeviceOpenedListener listener, Handler handler) {
          final OnDeviceOpenedListener listenerF = listener;
          final Handler handlerF = handler;
  
          IMidiDeviceOpenCallback callback = new IMidiDeviceOpenCallback.Stub() {
              @Override
              public void onDeviceOpened(IMidiDeviceServer server, IBinder deviceToken) {
                  MidiDevice device = null;
                  if (server != null) {
                      try {
                          // fetch MidiDeviceInfo from the server
                          MidiDeviceInfo deviceInfo = server.getDeviceInfo();
                          device = new MidiDevice(deviceInfo, server, mService, mToken, deviceToken);
                      } catch (RemoteException e) {
                          Log.e(TAG, "remote exception in getDeviceInfo()");
                      }
                  }
                  sendOpenDeviceResponse(device, listenerF, handlerF);
              }
          };
  
          try {
              mService.openBluetoothDevice(mToken, bluetoothDevice, callback);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /** @hide */
      public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
              int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
              Bundle properties, int type, MidiDeviceServer.Callback callback) {
          try {
              MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
                      numOutputPorts, callback);
              MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
                      inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames,
                      properties, type);
              if (deviceInfo == null) {
                  Log.e(TAG, "registerVirtualDevice failed");
                  return null;
              }
              return server;
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  }
  