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.os.IBinder;
  import android.os.ParcelFileDescriptor;
  import android.os.RemoteException;
  import android.util.Log;
  
  import com.android.internal.midi.MidiDispatcher;
  
  import dalvik.system.CloseGuard;
  
  import libcore.io.IoUtils;
  
  import java.io.Closeable;
  import java.io.FileDescriptor;
  import java.io.FileInputStream;
  import java.io.IOException;
  
  /**
   * This class is used for receiving data from a port on a MIDI device
   */
  public final class MidiOutputPort extends MidiSender implements Closeable {
      private static final String TAG = "MidiOutputPort";
  
      private IMidiDeviceServer mDeviceServer;
      private final IBinder mToken;
      private final int mPortNumber;
      private final FileInputStream mInputStream;
      private final MidiDispatcher mDispatcher = new MidiDispatcher();
  
      private final CloseGuard mGuard = CloseGuard.get();
      private boolean mIsClosed;
  
      // This thread reads MIDI events from a socket and distributes them to the list of
      // MidiReceivers attached to this device.
      private final Thread mThread = new Thread() {
          @Override
          public void run() {
              byte[] buffer = new byte[MidiPortImpl.MAX_PACKET_SIZE];
  
              try {
                  while (true) {
                      // read next event
                      int count = mInputStream.read(buffer);
                      if (count < 0) {
                          // This is the exit condition as read() returning <0 indicates
                          // that the pipe has been closed.
                          break;
                          // FIXME - inform receivers here?
                      }
  
                      int packetType = MidiPortImpl.getPacketType(buffer, count);
                      switch (packetType) {
                          case MidiPortImpl.PACKET_TYPE_DATA: {
                              int offset = MidiPortImpl.getDataOffset(buffer, count);
                              int size = MidiPortImpl.getDataSize(buffer, count);
                              long timestamp = MidiPortImpl.getPacketTimestamp(buffer, count);
  
                              // dispatch to all our receivers
                              mDispatcher.send(buffer, offset, size, timestamp);
                              break;
                          }
                          case MidiPortImpl.PACKET_TYPE_FLUSH:
                              mDispatcher.flush();
                              break;
                          default:
                              Log.e(TAG, "Unknown packet type " + packetType);
                              break;
                      }
                  } // while (true)
              } catch (IOException e) {
                  // FIXME report I/O failure?
                  // TODO: The comment above about the exit condition is not currently working
                  // as intended. The read from the closed pipe is throwing an error rather than
                  // returning <0, so this becomes (probably) not an error, but the exit case.
                  // This warrants further investigation;
                  // Silence the (probably) spurious error message.
                  // Log.e(TAG, "read failed", e);
              } finally {
                  IoUtils.closeQuietly(mInputStream);
              }
          }
      };
  
      /* package */ MidiOutputPort(IMidiDeviceServer server, IBinder token,
              FileDescriptor fd, int portNumber) {
          mDeviceServer = server;
          mToken = token;
          mPortNumber = portNumber;
          mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(new ParcelFileDescriptor(fd));
          mThread.start();
          mGuard.open("close");
      }
  
      /* package */ MidiOutputPort(FileDescriptor fd, int portNumber) {
          this(null, null, fd, portNumber);
      }
  
      /**
       * Returns the port number of this port
       *
       * @return the port's port number
       */
      public final int getPortNumber() {
          return mPortNumber;
      }
  
      @Override
      public void onConnect(MidiReceiver receiver) {
          mDispatcher.getSender().connect(receiver);
      }
  
      @Override
      public void onDisconnect(MidiReceiver receiver) {
          mDispatcher.getSender().disconnect(receiver);
      }
  
      @Override
      public void close() throws IOException {
          synchronized (mGuard) {
              if (mIsClosed) return;
  
              mGuard.close();
              mInputStream.close();
              if (mDeviceServer != null) {
                  try {
                      mDeviceServer.closePort(mToken);
                  } catch (RemoteException e) {
                      Log.e(TAG, "RemoteException in MidiOutputPort.close()");
                  }
              }
              mIsClosed = true;
          }
      }
  
      @Override
      protected void finalize() throws Throwable {
          try {
              if (mGuard != null) {
                  mGuard.warnIfOpen();
              }
  
              // not safe to make binder calls from finalize()
              mDeviceServer = null;
              close();
          } finally {
              super.finalize();
          }
      }
  }
  