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