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