MidiDeviceServer.java revision 5ff9e2a1719f78cddc7a23d6572ab15ab595dafd
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.Binder;
20import android.os.IBinder;
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
65    // for reporting device status
66    private final IBinder mDeviceStatusToken = new Binder();
67    private final boolean[] mInputPortBusy;
68    private final int[] mOutputPortOpenCount;
69
70    private final CloseGuard mGuard = CloseGuard.get();
71    private boolean mIsClosed;
72
73    private final Callback mCallback;
74
75    public interface Callback {
76        /**
77         * Called to notify when an our device status has changed
78         * @param server the {@link MidiDeviceServer} that changed
79         * @param status the {@link MidiDeviceStatus} for the device
80         */
81        public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status);
82    }
83
84    abstract private class PortClient implements IBinder.DeathRecipient {
85        final IBinder mToken;
86
87        PortClient(IBinder token) {
88            mToken = token;
89
90            try {
91                token.linkToDeath(this, 0);
92            } catch (RemoteException e) {
93                close();
94            }
95        }
96
97        abstract void close();
98
99        @Override
100        public void binderDied() {
101            close();
102        }
103    }
104
105    private class InputPortClient extends PortClient {
106        private final MidiOutputPort mOutputPort;
107
108        InputPortClient(IBinder token, MidiOutputPort outputPort) {
109            super(token);
110            mOutputPort = outputPort;
111        }
112
113        @Override
114        void close() {
115            mToken.unlinkToDeath(this, 0);
116            synchronized (mInputPortOutputPorts) {
117                int portNumber = mOutputPort.getPortNumber();
118                mInputPortOutputPorts[portNumber] = null;
119                mInputPortBusy[portNumber] = false;
120                updateDeviceStatus();
121            }
122            IoUtils.closeQuietly(mOutputPort);
123        }
124    }
125
126    private class OutputPortClient extends PortClient {
127        private final MidiInputPort mInputPort;
128
129        OutputPortClient(IBinder token, MidiInputPort inputPort) {
130            super(token);
131            mInputPort = inputPort;
132        }
133
134        @Override
135        void close() {
136            mToken.unlinkToDeath(this, 0);
137            int portNumber = mInputPort.getPortNumber();
138            MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
139            synchronized (dispatcher) {
140                dispatcher.getSender().disconnect(mInputPort);
141                int openCount = dispatcher.getReceiverCount();
142                mOutputPortOpenCount[portNumber] = openCount;
143                updateDeviceStatus();
144           }
145
146            mInputPorts.remove(mInputPort);
147            IoUtils.closeQuietly(mInputPort);
148        }
149    }
150
151    private final HashMap<IBinder, PortClient> mPortClients = new HashMap<IBinder, PortClient>();
152
153    // Binder interface stub for receiving connection requests from clients
154    private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
155
156        @Override
157        public ParcelFileDescriptor openInputPort(IBinder token, int portNumber) {
158            if (mDeviceInfo.isPrivate()) {
159                if (Binder.getCallingUid() != Process.myUid()) {
160                    throw new SecurityException("Can't access private device from different UID");
161                }
162            }
163
164            if (portNumber < 0 || portNumber >= mInputPortCount) {
165                Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
166                return null;
167            }
168
169            synchronized (mInputPortOutputPorts) {
170                if (mInputPortOutputPorts[portNumber] != null) {
171                    Log.d(TAG, "port " + portNumber + " already open");
172                    return null;
173                }
174
175                try {
176                    ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
177                                                        OsConstants.SOCK_SEQPACKET);
178                    MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
179                    mInputPortOutputPorts[portNumber] = outputPort;
180                    outputPort.connect(mInputPortReceivers[portNumber]);
181                    InputPortClient client = new InputPortClient(token, outputPort);
182                    synchronized (mPortClients) {
183                        mPortClients.put(token, client);
184                    }
185                    mInputPortBusy[portNumber] = true;
186                    updateDeviceStatus();
187                    return pair[1];
188                } catch (IOException e) {
189                    Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
190                    return null;
191                }
192            }
193        }
194
195        @Override
196        public ParcelFileDescriptor openOutputPort(IBinder token, int portNumber) {
197            if (mDeviceInfo.isPrivate()) {
198                if (Binder.getCallingUid() != Process.myUid()) {
199                    throw new SecurityException("Can't access private device from different UID");
200                }
201            }
202
203            if (portNumber < 0 || portNumber >= mOutputPortCount) {
204                Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
205                return null;
206            }
207
208            try {
209                ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
210                                                    OsConstants.SOCK_SEQPACKET);
211                MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
212                MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
213                synchronized (dispatcher) {
214                    dispatcher.getSender().connect(inputPort);
215                    int openCount = dispatcher.getReceiverCount();
216                    mOutputPortOpenCount[portNumber] = openCount;
217                    updateDeviceStatus();
218                }
219
220                mInputPorts.add(inputPort);
221                OutputPortClient client = new OutputPortClient(token, inputPort);
222                synchronized (mPortClients) {
223                    mPortClients.put(token, client);
224                }
225                return pair[1];
226            } catch (IOException e) {
227                Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
228                return null;
229            }
230        }
231
232        @Override
233        public void closePort(IBinder token) {
234            synchronized (mPortClients) {
235                PortClient client = mPortClients.remove(token);
236                if (client != null) {
237                    client.close();
238                }
239            }
240        }
241
242        @Override
243        public void connectPorts(IBinder token, ParcelFileDescriptor pfd,
244                int outputPortNumber) {
245            MidiInputPort inputPort = new MidiInputPort(pfd, outputPortNumber);
246            mOutputPortDispatchers[outputPortNumber].getSender().connect(inputPort);
247            mInputPorts.add(inputPort);
248            OutputPortClient client = new OutputPortClient(token, inputPort);
249            synchronized (mPortClients) {
250                mPortClients.put(token, client);
251            }
252        }
253    };
254
255    /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
256            int numOutputPorts, Callback callback) {
257        mMidiManager = midiManager;
258        mInputPortReceivers = inputPortReceivers;
259        mInputPortCount = inputPortReceivers.length;
260        mOutputPortCount = numOutputPorts;
261        mCallback = callback;
262
263        mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
264
265        mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
266        for (int i = 0; i < numOutputPorts; i++) {
267            mOutputPortDispatchers[i] = new MidiDispatcher();
268        }
269
270        mInputPortBusy = new boolean[mInputPortCount];
271        mOutputPortOpenCount = new int[numOutputPorts];
272
273        mGuard.open("close");
274    }
275
276    /* package */ IMidiDeviceServer getBinderInterface() {
277        return mServer;
278    }
279
280    /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
281        if (mDeviceInfo != null) {
282            throw new IllegalStateException("setDeviceInfo should only be called once");
283        }
284        mDeviceInfo = deviceInfo;
285    }
286
287    private void updateDeviceStatus() {
288        // clear calling identity, since we may be in a Binder call from one of our clients
289        long identityToken = Binder.clearCallingIdentity();
290
291        MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortBusy,
292                mOutputPortOpenCount);
293        if (mCallback != null) {
294            mCallback.onDeviceStatusChanged(this, status);
295        }
296        try {
297            mMidiManager.setDeviceStatus(mDeviceStatusToken, status);
298        } catch (RemoteException e) {
299            Log.e(TAG, "RemoteException in updateDeviceStatus");
300        } finally {
301            Binder.restoreCallingIdentity(identityToken);
302        }
303    }
304
305    @Override
306    public void close() throws IOException {
307        synchronized (mGuard) {
308            if (mIsClosed) return;
309            mGuard.close();
310
311            for (int i = 0; i < mInputPortCount; i++) {
312                MidiOutputPort outputPort = mInputPortOutputPorts[i];
313                if (outputPort != null) {
314                    IoUtils.closeQuietly(outputPort);
315                    mInputPortOutputPorts[i] = null;
316                }
317            }
318            for (MidiInputPort inputPort : mInputPorts) {
319                IoUtils.closeQuietly(inputPort);
320            }
321            mInputPorts.clear();
322            try {
323                mMidiManager.unregisterDeviceServer(mServer);
324            } catch (RemoteException e) {
325                Log.e(TAG, "RemoteException in unregisterDeviceServer");
326            }
327            mIsClosed = true;
328        }
329    }
330
331    @Override
332    protected void finalize() throws Throwable {
333        try {
334            mGuard.warnIfOpen();
335            close();
336        } finally {
337            super.finalize();
338        }
339    }
340
341    /**
342     * Returns an array of {@link MidiReceiver} for the device's output ports.
343     * Clients can use these receivers to send data out the device's output ports.
344     * @return array of MidiReceivers
345     */
346    public MidiReceiver[] getOutputPortReceivers() {
347        MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
348        System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
349        return receivers;
350    }
351}
352