MidiDeviceServer.java revision 3b7664589be22ddad34b72e11ced937d48660ebb
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.os.IBinder;
20import android.os.Binder;
21import android.os.ParcelFileDescriptor;
22import android.os.Process;
23import android.os.RemoteException;
24import android.system.OsConstants;
25import android.util.Log;
26
27import libcore.io.IoUtils;
28
29import java.io.Closeable;
30import java.io.IOException;
31
32/**
33 * Internal class used for providing an implementation for a MIDI device.
34 *
35 * @hide
36 */
37public final class MidiDeviceServer implements Closeable {
38    private static final String TAG = "MidiDeviceServer";
39
40    private final IMidiManager mMidiManager;
41
42    // MidiDeviceInfo for the device implemented by this server
43    private MidiDeviceInfo mDeviceInfo;
44    private final int mInputPortCount;
45    private final int mOutputPortCount;
46
47    // MidiReceivers for receiving data on our input ports
48    private final MidiReceiver[] mInputPortReceivers;
49
50    // MidiDispatchers for sending data on our output ports
51    private MidiDispatcher[] mOutputPortDispatchers;
52
53    // MidiOutputPorts for clients connected to our input ports
54    private final MidiOutputPort[] mInputPortOutputPorts;
55
56    // Binder interface stub for receiving connection requests from clients
57    private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
58
59        @Override
60        public ParcelFileDescriptor openInputPort(int portNumber) {
61            if (mDeviceInfo.isPrivate()) {
62                if (Binder.getCallingUid() != Process.myUid()) {
63                    throw new SecurityException("Can't access private device from different UID");
64                }
65            }
66
67            if (portNumber < 0 || portNumber >= mInputPortCount) {
68                Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
69                return null;
70            }
71
72            synchronized (mInputPortOutputPorts) {
73                if (mInputPortOutputPorts[portNumber] != null) {
74                    Log.d(TAG, "port " + portNumber + " already open");
75                    return null;
76                }
77
78                try {
79                    ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
80                                                        OsConstants.SOCK_SEQPACKET);
81                    final MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
82                    mInputPortOutputPorts[portNumber] = outputPort;
83                    final int portNumberF = portNumber;
84                    final MidiReceiver inputPortReceviver = mInputPortReceivers[portNumber];
85
86                    outputPort.connect(new MidiReceiver() {
87                        @Override
88                        public void receive(byte[] msg, int offset, int count, long timestamp)
89                                throws IOException {
90                            try {
91                                inputPortReceviver.receive(msg, offset, count, timestamp);
92                            } catch (IOException e) {
93                                IoUtils.closeQuietly(mInputPortOutputPorts[portNumberF]);
94                                mInputPortOutputPorts[portNumberF] = null;
95                                // FIXME also flush the receiver
96                            }
97                        }
98                    });
99
100                    return pair[1];
101                } catch (IOException e) {
102                    Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
103                    return null;
104                }
105            }
106        }
107
108        @Override
109        public ParcelFileDescriptor openOutputPort(int portNumber) {
110            if (mDeviceInfo.isPrivate()) {
111                if (Binder.getCallingUid() != Process.myUid()) {
112                    throw new SecurityException("Can't access private device from different UID");
113                }
114            }
115
116            if (portNumber < 0 || portNumber >= mOutputPortCount) {
117                Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
118                return null;
119            }
120
121            try {
122                ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
123                                                    OsConstants.SOCK_SEQPACKET);
124                final MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
125                final MidiSender sender = mOutputPortDispatchers[portNumber].getSender();
126                sender.connect(new MidiReceiver() {
127                        @Override
128                        public void receive(byte[] msg, int offset, int count, long timestamp)
129                                throws IOException {
130                            try {
131                                inputPort.receive(msg, offset, count, timestamp);
132                            } catch (IOException e) {
133                                IoUtils.closeQuietly(inputPort);
134                                sender.disconnect(this);
135                                // FIXME also flush the receiver?
136                            }
137                        }
138                    });
139
140                return pair[1];
141            } catch (IOException e) {
142                Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
143                return null;
144            }
145        }
146    };
147
148    /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
149            int numOutputPorts) {
150        mMidiManager = midiManager;
151        mInputPortReceivers = inputPortReceivers;
152        mInputPortCount = inputPortReceivers.length;
153        mOutputPortCount = numOutputPorts;
154
155        mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
156
157        mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
158        for (int i = 0; i < numOutputPorts; i++) {
159            mOutputPortDispatchers[i] = new MidiDispatcher();
160        }
161    }
162
163    /* package */ IMidiDeviceServer getBinderInterface() {
164        return mServer;
165    }
166
167    /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
168        if (mDeviceInfo != null) {
169            throw new IllegalStateException("setDeviceInfo should only be called once");
170        }
171        mDeviceInfo = deviceInfo;
172    }
173
174    @Override
175    public void close() throws IOException {
176        try {
177            // FIXME - close input and output ports too?
178            mMidiManager.unregisterDeviceServer(mServer);
179        } catch (RemoteException e) {
180            Log.e(TAG, "RemoteException in unregisterDeviceServer");
181        }
182    }
183
184    /**
185     * Returns an array of {@link MidiReceiver} for the device's output ports.
186     * Clients can use these receivers to send data out the device's output ports.
187     * @return array of MidiReceivers
188     */
189    public MidiReceiver[] getOutputPortReceivers() {
190        MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
191        System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
192        return receivers;
193    }
194}
195