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