MidiDeviceServer.java revision 46326e59a0a19367d4158c027d56d4b8440e8d3d
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 dalvik.system.CloseGuard;
28
29import libcore.io.IoUtils;
30
31import java.io.Closeable;
32import java.io.IOException;
33import java.util.HashMap;
34import java.util.concurrent.CopyOnWriteArrayList;
35
36/**
37 * Internal class used for providing an implementation for a MIDI device.
38 *
39 * @hide
40 */
41public final class MidiDeviceServer implements Closeable {
42    private static final String TAG = "MidiDeviceServer";
43
44    private final IMidiManager mMidiManager;
45
46    // MidiDeviceInfo for the device implemented by this server
47    private MidiDeviceInfo mDeviceInfo;
48    private final int mInputPortCount;
49    private final int mOutputPortCount;
50
51    // MidiReceivers for receiving data on our input ports
52    private final MidiReceiver[] mInputPortReceivers;
53
54    // MidiDispatchers for sending data on our output ports
55    private MidiDispatcher[] mOutputPortDispatchers;
56
57    // MidiOutputPorts for clients connected to our input ports
58    private final MidiOutputPort[] mInputPortOutputPorts;
59
60    // List of all MidiInputPorts we created
61    private final CopyOnWriteArrayList<MidiInputPort> mInputPorts
62            = new CopyOnWriteArrayList<MidiInputPort>();
63
64    private final CloseGuard mGuard = CloseGuard.get();
65
66    abstract private class PortClient implements IBinder.DeathRecipient {
67        final IBinder mToken;
68
69        PortClient(IBinder token) {
70            mToken = token;
71
72            try {
73                token.linkToDeath(this, 0);
74            } catch (RemoteException e) {
75                close();
76            }
77        }
78
79        abstract void close();
80
81        @Override
82        public void binderDied() {
83            close();
84        }
85    }
86
87    private class InputPortClient extends PortClient {
88        private final MidiOutputPort mOutputPort;
89
90        InputPortClient(IBinder token, MidiOutputPort outputPort) {
91            super(token);
92            mOutputPort = outputPort;
93        }
94
95        @Override
96        void close() {
97            mToken.unlinkToDeath(this, 0);
98            synchronized (mInputPortOutputPorts) {
99                mInputPortOutputPorts[mOutputPort.getPortNumber()] = null;
100            }
101            IoUtils.closeQuietly(mOutputPort);
102        }
103    }
104
105    private class OutputPortClient extends PortClient {
106        private final MidiInputPort mInputPort;
107
108        OutputPortClient(IBinder token, MidiInputPort inputPort) {
109            super(token);
110            mInputPort = inputPort;
111        }
112
113        @Override
114        void close() {
115            mToken.unlinkToDeath(this, 0);
116            mOutputPortDispatchers[mInputPort.getPortNumber()].getSender().disconnect(mInputPort);
117            mInputPorts.remove(mInputPort);
118            IoUtils.closeQuietly(mInputPort);
119        }
120    }
121
122    private final HashMap<IBinder, PortClient> mPortClients = new HashMap<IBinder, PortClient>();
123
124    // Binder interface stub for receiving connection requests from clients
125    private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
126
127        @Override
128        public ParcelFileDescriptor openInputPort(IBinder token, int portNumber) {
129            if (mDeviceInfo.isPrivate()) {
130                if (Binder.getCallingUid() != Process.myUid()) {
131                    throw new SecurityException("Can't access private device from different UID");
132                }
133            }
134
135            if (portNumber < 0 || portNumber >= mInputPortCount) {
136                Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
137                return null;
138            }
139
140            synchronized (mInputPortOutputPorts) {
141                if (mInputPortOutputPorts[portNumber] != null) {
142                    Log.d(TAG, "port " + portNumber + " already open");
143                    return null;
144                }
145
146                try {
147                    ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
148                                                        OsConstants.SOCK_SEQPACKET);
149                    MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
150                    mInputPortOutputPorts[portNumber] = outputPort;
151                    outputPort.connect(mInputPortReceivers[portNumber]);
152                    InputPortClient client = new InputPortClient(token, outputPort);
153                    synchronized (mPortClients) {
154                        mPortClients.put(token, client);
155                    }
156                    return pair[1];
157                } catch (IOException e) {
158                    Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
159                    return null;
160                }
161            }
162        }
163
164        @Override
165        public ParcelFileDescriptor openOutputPort(IBinder token, int portNumber) {
166            if (mDeviceInfo.isPrivate()) {
167                if (Binder.getCallingUid() != Process.myUid()) {
168                    throw new SecurityException("Can't access private device from different UID");
169                }
170            }
171
172            if (portNumber < 0 || portNumber >= mOutputPortCount) {
173                Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
174                return null;
175            }
176
177            try {
178                ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
179                                                    OsConstants.SOCK_SEQPACKET);
180                MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
181                mOutputPortDispatchers[portNumber].getSender().connect(inputPort);
182                mInputPorts.add(inputPort);
183                OutputPortClient client = new OutputPortClient(token, inputPort);
184                synchronized (mPortClients) {
185                    mPortClients.put(token, client);
186                }
187                return pair[1];
188            } catch (IOException e) {
189                Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
190                return null;
191            }
192        }
193
194        @Override
195        public void closePort(IBinder token) {
196            synchronized (mPortClients) {
197                PortClient client = mPortClients.remove(token);
198                if (client != null) {
199                    client.close();
200                }
201            }
202        }
203
204        @Override
205        public void connectPorts(IBinder token, ParcelFileDescriptor pfd,
206                int outputPortNumber) {
207            MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber);
208            mOutputPortDispatchers[outputPortNumber].getSender().connect(inputPort);
209            mInputPorts.add(inputPort);
210            OutputPortClient client = new OutputPortClient(token, inputPort);
211            synchronized (mPortClients) {
212                mPortClients.put(token, client);
213            }
214        }
215    };
216
217    /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
218            int numOutputPorts) {
219        mMidiManager = midiManager;
220        mInputPortReceivers = inputPortReceivers;
221        mInputPortCount = inputPortReceivers.length;
222        mOutputPortCount = numOutputPorts;
223
224        mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
225
226        mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
227        for (int i = 0; i < numOutputPorts; i++) {
228            mOutputPortDispatchers[i] = new MidiDispatcher();
229        }
230
231        mGuard.open("close");
232    }
233
234    /* package */ IMidiDeviceServer getBinderInterface() {
235        return mServer;
236    }
237
238    /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
239        if (mDeviceInfo != null) {
240            throw new IllegalStateException("setDeviceInfo should only be called once");
241        }
242        mDeviceInfo = deviceInfo;
243    }
244
245    @Override
246    public void close() throws IOException {
247        synchronized (mGuard) {
248            mGuard.close();
249
250            for (int i = 0; i < mInputPortCount; i++) {
251                MidiOutputPort outputPort = mInputPortOutputPorts[i];
252                if (outputPort != null) {
253                    IoUtils.closeQuietly(outputPort);
254                    mInputPortOutputPorts[i] = null;
255                }
256            }
257            for (MidiInputPort inputPort : mInputPorts) {
258                IoUtils.closeQuietly(inputPort);
259            }
260            mInputPorts.clear();
261            try {
262                mMidiManager.unregisterDeviceServer(mServer);
263            } catch (RemoteException e) {
264                Log.e(TAG, "RemoteException in unregisterDeviceServer");
265            }
266        }
267    }
268
269    @Override
270    protected void finalize() throws Throwable {
271        try {
272            mGuard.warnIfOpen();
273            close();
274        } finally {
275            super.finalize();
276        }
277    }
278
279    /**
280     * Returns an array of {@link MidiReceiver} for the device's output ports.
281     * Clients can use these receivers to send data out the device's output ports.
282     * @return array of MidiReceivers
283     */
284    public MidiReceiver[] getOutputPortReceivers() {
285        MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
286        System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
287        return receivers;
288    }
289}
290