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