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 
17 package android.media.midi;
18 
19 import android.os.Binder;
20 import android.os.IBinder;
21 import android.os.Process;
22 import android.os.RemoteException;
23 import android.system.ErrnoException;
24 import android.system.Os;
25 import android.system.OsConstants;
26 import android.util.Log;
27 
28 import com.android.internal.midi.MidiDispatcher;
29 
30 import dalvik.system.CloseGuard;
31 
32 import libcore.io.IoUtils;
33 
34 import java.io.Closeable;
35 import java.io.FileDescriptor;
36 import java.io.IOException;
37 import java.util.HashMap;
38 import java.util.concurrent.CopyOnWriteArrayList;
39 
40 /**
41  * Internal class used for providing an implementation for a MIDI device.
42  *
43  * @hide
44  */
45 public final class MidiDeviceServer implements Closeable {
46     private static final String TAG = "MidiDeviceServer";
47 
48     private final IMidiManager mMidiManager;
49 
50     // MidiDeviceInfo for the device implemented by this server
51     private MidiDeviceInfo mDeviceInfo;
52     private final int mInputPortCount;
53     private final int mOutputPortCount;
54 
55     // MidiReceivers for receiving data on our input ports
56     private final MidiReceiver[] mInputPortReceivers;
57 
58     // MidiDispatchers for sending data on our output ports
59     private MidiDispatcher[] mOutputPortDispatchers;
60 
61     // MidiOutputPorts for clients connected to our input ports
62     private final MidiOutputPort[] mInputPortOutputPorts;
63 
64     // List of all MidiInputPorts we created
65     private final CopyOnWriteArrayList<MidiInputPort> mInputPorts
66             = new CopyOnWriteArrayList<MidiInputPort>();
67 
68 
69     // for reporting device status
70     private final boolean[] mInputPortOpen;
71     private final int[] mOutputPortOpenCount;
72 
73     private final CloseGuard mGuard = CloseGuard.get();
74     private boolean mIsClosed;
75 
76     private final Callback mCallback;
77 
78     private final HashMap<IBinder, PortClient> mPortClients = new HashMap<IBinder, PortClient>();
79     private final HashMap<MidiInputPort, PortClient> mInputPortClients =
80             new HashMap<MidiInputPort, PortClient>();
81 
82     public interface Callback {
83         /**
84          * Called to notify when an our device status has changed
85          * @param server the {@link MidiDeviceServer} that changed
86          * @param status the {@link MidiDeviceStatus} for the device
87          */
onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status)88         public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status);
89 
90         /**
91          * Called to notify when the device is closed
92          */
onClose()93         public void onClose();
94     }
95 
96     abstract private class PortClient implements IBinder.DeathRecipient {
97         final IBinder mToken;
98 
PortClient(IBinder token)99         PortClient(IBinder token) {
100             mToken = token;
101 
102             try {
103                 token.linkToDeath(this, 0);
104             } catch (RemoteException e) {
105                 close();
106             }
107         }
108 
close()109         abstract void close();
110 
getInputPort()111         MidiInputPort getInputPort() {
112             return null;
113         }
114 
115         @Override
binderDied()116         public void binderDied() {
117             close();
118         }
119     }
120 
121     private class InputPortClient extends PortClient {
122         private final MidiOutputPort mOutputPort;
123 
InputPortClient(IBinder token, MidiOutputPort outputPort)124         InputPortClient(IBinder token, MidiOutputPort outputPort) {
125             super(token);
126             mOutputPort = outputPort;
127         }
128 
129         @Override
close()130         void close() {
131             mToken.unlinkToDeath(this, 0);
132             synchronized (mInputPortOutputPorts) {
133                 int portNumber = mOutputPort.getPortNumber();
134                 mInputPortOutputPorts[portNumber] = null;
135                 mInputPortOpen[portNumber] = false;
136                 updateDeviceStatus();
137             }
138             IoUtils.closeQuietly(mOutputPort);
139         }
140     }
141 
142     private class OutputPortClient extends PortClient {
143         private final MidiInputPort mInputPort;
144 
OutputPortClient(IBinder token, MidiInputPort inputPort)145         OutputPortClient(IBinder token, MidiInputPort inputPort) {
146             super(token);
147             mInputPort = inputPort;
148         }
149 
150         @Override
close()151         void close() {
152             mToken.unlinkToDeath(this, 0);
153             int portNumber = mInputPort.getPortNumber();
154             MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
155             synchronized (dispatcher) {
156                 dispatcher.getSender().disconnect(mInputPort);
157                 int openCount = dispatcher.getReceiverCount();
158                 mOutputPortOpenCount[portNumber] = openCount;
159                 updateDeviceStatus();
160            }
161 
162             mInputPorts.remove(mInputPort);
163             IoUtils.closeQuietly(mInputPort);
164         }
165 
166         @Override
getInputPort()167         MidiInputPort getInputPort() {
168             return mInputPort;
169         }
170     }
171 
createSeqPacketSocketPair()172     private static FileDescriptor[] createSeqPacketSocketPair() throws IOException {
173         try {
174             final FileDescriptor fd0 = new FileDescriptor();
175             final FileDescriptor fd1 = new FileDescriptor();
176             Os.socketpair(OsConstants.AF_UNIX, OsConstants.SOCK_SEQPACKET, 0, fd0, fd1);
177             return new FileDescriptor[] { fd0, fd1 };
178         } catch (ErrnoException e) {
179             throw e.rethrowAsIOException();
180         }
181     }
182 
183     // Binder interface stub for receiving connection requests from clients
184     private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
185 
186         @Override
187         public FileDescriptor openInputPort(IBinder token, int portNumber) {
188             if (mDeviceInfo.isPrivate()) {
189                 if (Binder.getCallingUid() != Process.myUid()) {
190                     throw new SecurityException("Can't access private device from different UID");
191                 }
192             }
193 
194             if (portNumber < 0 || portNumber >= mInputPortCount) {
195                 Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
196                 return null;
197             }
198 
199             synchronized (mInputPortOutputPorts) {
200                 if (mInputPortOutputPorts[portNumber] != null) {
201                     Log.d(TAG, "port " + portNumber + " already open");
202                     return null;
203                 }
204 
205                 try {
206                     FileDescriptor[] pair = createSeqPacketSocketPair();
207                     MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
208                     mInputPortOutputPorts[portNumber] = outputPort;
209                     outputPort.connect(mInputPortReceivers[portNumber]);
210                     InputPortClient client = new InputPortClient(token, outputPort);
211                     synchronized (mPortClients) {
212                         mPortClients.put(token, client);
213                     }
214                     mInputPortOpen[portNumber] = true;
215                     updateDeviceStatus();
216                     return pair[1];
217                 } catch (IOException e) {
218                     Log.e(TAG, "unable to create FileDescriptors in openInputPort");
219                     return null;
220                 }
221             }
222         }
223 
224         @Override
225         public FileDescriptor openOutputPort(IBinder token, int portNumber) {
226             if (mDeviceInfo.isPrivate()) {
227                 if (Binder.getCallingUid() != Process.myUid()) {
228                     throw new SecurityException("Can't access private device from different UID");
229                 }
230             }
231 
232             if (portNumber < 0 || portNumber >= mOutputPortCount) {
233                 Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
234                 return null;
235             }
236 
237             try {
238                 FileDescriptor[] pair = createSeqPacketSocketPair();
239                 MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
240                 // Undo the default blocking-mode of the server-side socket for
241                 // physical devices to avoid stalling the Java device handler if
242                 // client app code gets stuck inside 'onSend' handler.
243                 if (mDeviceInfo.getType() != MidiDeviceInfo.TYPE_VIRTUAL) {
244                     IoUtils.setBlocking(pair[0], false);
245                 }
246                 MidiDispatcher dispatcher = mOutputPortDispatchers[portNumber];
247                 synchronized (dispatcher) {
248                     dispatcher.getSender().connect(inputPort);
249                     int openCount = dispatcher.getReceiverCount();
250                     mOutputPortOpenCount[portNumber] = openCount;
251                     updateDeviceStatus();
252                 }
253 
254                 mInputPorts.add(inputPort);
255                 OutputPortClient client = new OutputPortClient(token, inputPort);
256                 synchronized (mPortClients) {
257                     mPortClients.put(token, client);
258                 }
259                 synchronized (mInputPortClients) {
260                     mInputPortClients.put(inputPort, client);
261                 }
262                 return pair[1];
263             } catch (IOException e) {
264                 Log.e(TAG, "unable to create FileDescriptors in openOutputPort");
265                 return null;
266             }
267         }
268 
269         @Override
270         public void closePort(IBinder token) {
271             MidiInputPort inputPort = null;
272             synchronized (mPortClients) {
273                 PortClient client = mPortClients.remove(token);
274                 if (client != null) {
275                     inputPort = client.getInputPort();
276                     client.close();
277                 }
278             }
279             if (inputPort != null) {
280                 synchronized (mInputPortClients) {
281                     mInputPortClients.remove(inputPort);
282                 }
283             }
284         }
285 
286         @Override
287         public void closeDevice() {
288             if (mCallback != null) {
289                 mCallback.onClose();
290             }
291             IoUtils.closeQuietly(MidiDeviceServer.this);
292         }
293 
294         @Override
295         public int connectPorts(IBinder token, FileDescriptor fd,
296                 int outputPortNumber) {
297             MidiInputPort inputPort = new MidiInputPort(fd, outputPortNumber);
298             MidiDispatcher dispatcher = mOutputPortDispatchers[outputPortNumber];
299             synchronized (dispatcher) {
300                 dispatcher.getSender().connect(inputPort);
301                 int openCount = dispatcher.getReceiverCount();
302                 mOutputPortOpenCount[outputPortNumber] = openCount;
303                 updateDeviceStatus();
304             }
305 
306             mInputPorts.add(inputPort);
307             OutputPortClient client = new OutputPortClient(token, inputPort);
308             synchronized (mPortClients) {
309                 mPortClients.put(token, client);
310             }
311             synchronized (mInputPortClients) {
312                 mInputPortClients.put(inputPort, client);
313             }
314             return Process.myPid(); // for caller to detect same process ID
315         }
316 
317         @Override
318         public MidiDeviceInfo getDeviceInfo() {
319             return mDeviceInfo;
320         }
321 
322         @Override
323         public void setDeviceInfo(MidiDeviceInfo deviceInfo) {
324             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
325                 throw new SecurityException("setDeviceInfo should only be called by MidiService");
326             }
327             if (mDeviceInfo != null) {
328                 throw new IllegalStateException("setDeviceInfo should only be called once");
329             }
330             mDeviceInfo = deviceInfo;
331         }
332     };
333 
334     // Constructor for MidiManager.createDeviceServer()
MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers, int numOutputPorts, Callback callback)335     /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
336             int numOutputPorts, Callback callback) {
337         mMidiManager = midiManager;
338         mInputPortReceivers = inputPortReceivers;
339         mInputPortCount = inputPortReceivers.length;
340         mOutputPortCount = numOutputPorts;
341         mCallback = callback;
342 
343         mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
344 
345         mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
346         for (int i = 0; i < numOutputPorts; i++) {
347             mOutputPortDispatchers[i] = new MidiDispatcher(mInputPortFailureHandler);
348         }
349 
350         mInputPortOpen = new boolean[mInputPortCount];
351         mOutputPortOpenCount = new int[numOutputPorts];
352 
353         mGuard.open("close");
354     }
355 
356     private final MidiDispatcher.MidiReceiverFailureHandler mInputPortFailureHandler =
357             new MidiDispatcher.MidiReceiverFailureHandler() {
358                 public void onReceiverFailure(MidiReceiver receiver, IOException failure) {
359                     Log.e(TAG, "MidiInputPort failed to send data", failure);
360                     PortClient client = null;
361                     synchronized (mInputPortClients) {
362                         client = mInputPortClients.remove(receiver);
363                     }
364                     if (client != null) {
365                         client.close();
366                     }
367                 }
368             };
369 
370     // Constructor for MidiDeviceService.onCreate()
MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers, MidiDeviceInfo deviceInfo, Callback callback)371     /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
372            MidiDeviceInfo deviceInfo, Callback callback) {
373         this(midiManager, inputPortReceivers, deviceInfo.getOutputPortCount(), callback);
374         mDeviceInfo = deviceInfo;
375     }
376 
getBinderInterface()377     /* package */ IMidiDeviceServer getBinderInterface() {
378         return mServer;
379     }
380 
asBinder()381     public IBinder asBinder() {
382         return mServer.asBinder();
383     }
384 
updateDeviceStatus()385     private void updateDeviceStatus() {
386         // clear calling identity, since we may be in a Binder call from one of our clients
387         long identityToken = Binder.clearCallingIdentity();
388 
389         MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortOpen,
390                 mOutputPortOpenCount);
391         if (mCallback != null) {
392             mCallback.onDeviceStatusChanged(this, status);
393         }
394         try {
395             mMidiManager.setDeviceStatus(mServer, status);
396         } catch (RemoteException e) {
397             Log.e(TAG, "RemoteException in updateDeviceStatus");
398         } finally {
399             Binder.restoreCallingIdentity(identityToken);
400         }
401     }
402 
403     @Override
close()404     public void close() throws IOException {
405         synchronized (mGuard) {
406             if (mIsClosed) return;
407             mGuard.close();
408 
409             for (int i = 0; i < mInputPortCount; i++) {
410                 MidiOutputPort outputPort = mInputPortOutputPorts[i];
411                 if (outputPort != null) {
412                     IoUtils.closeQuietly(outputPort);
413                     mInputPortOutputPorts[i] = null;
414                 }
415             }
416             for (MidiInputPort inputPort : mInputPorts) {
417                 IoUtils.closeQuietly(inputPort);
418             }
419             mInputPorts.clear();
420             try {
421                 mMidiManager.unregisterDeviceServer(mServer);
422             } catch (RemoteException e) {
423                 Log.e(TAG, "RemoteException in unregisterDeviceServer");
424             }
425             mIsClosed = true;
426         }
427     }
428 
429     @Override
finalize()430     protected void finalize() throws Throwable {
431         try {
432             if (mGuard != null) {
433                 mGuard.warnIfOpen();
434             }
435 
436             close();
437         } finally {
438             super.finalize();
439         }
440     }
441 
442     /**
443      * Returns an array of {@link MidiReceiver} for the device's output ports.
444      * Clients can use these receivers to send data out the device's output ports.
445      * @return array of MidiReceivers
446      */
getOutputPortReceivers()447     public MidiReceiver[] getOutputPortReceivers() {
448         MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
449         System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
450         return receivers;
451     }
452 }
453