1 /*
2  * Copyright (C) 2015 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 com.android.internal.midi;
18 
19 import android.media.midi.MidiReceiver;
20 import android.media.midi.MidiSender;
21 
22 import java.io.IOException;
23 import java.util.concurrent.CopyOnWriteArrayList;
24 
25 /**
26  * Utility class for dispatching MIDI data to a list of {@link android.media.midi.MidiReceiver}s.
27  * This class subclasses {@link android.media.midi.MidiReceiver} and dispatches any data it receives
28  * to its receiver list. Any receivers that throw an exception upon receiving data will
29  * be automatically removed from the receiver list. If a MidiReceiverFailureHandler has been
30  * provided to the MidiDispatcher, it will be notified about the failure, but the exception
31  * itself will be swallowed.
32  */
33 public final class MidiDispatcher extends MidiReceiver {
34 
35     // MidiDispatcher's client and MidiReceiver's owner can be different
36     // classes (e.g. MidiDeviceService is a client, but MidiDeviceServer is
37     // the owner), and errors occuring during sending need to be reported
38     // to the owner rather than to the sender.
39     //
40     // Note that the callbacks will be called on the sender's thread.
41     public interface MidiReceiverFailureHandler {
onReceiverFailure(MidiReceiver receiver, IOException failure)42         void onReceiverFailure(MidiReceiver receiver, IOException failure);
43     }
44 
45     private final MidiReceiverFailureHandler mFailureHandler;
46     private final CopyOnWriteArrayList<MidiReceiver> mReceivers
47             = new CopyOnWriteArrayList<MidiReceiver>();
48 
49     private final MidiSender mSender = new MidiSender() {
50         @Override
51         public void onConnect(MidiReceiver receiver) {
52             mReceivers.add(receiver);
53         }
54 
55         @Override
56         public void onDisconnect(MidiReceiver receiver) {
57             mReceivers.remove(receiver);
58         }
59     };
60 
MidiDispatcher()61     public MidiDispatcher() {
62         this(null);
63     }
64 
MidiDispatcher(MidiReceiverFailureHandler failureHandler)65     public MidiDispatcher(MidiReceiverFailureHandler failureHandler) {
66         mFailureHandler = failureHandler;
67     }
68 
69     /**
70      * Returns the number of {@link android.media.midi.MidiReceiver}s this dispatcher contains.
71      * @return the number of receivers
72      */
getReceiverCount()73     public int getReceiverCount() {
74         return mReceivers.size();
75     }
76 
77     /**
78      * Returns a {@link android.media.midi.MidiSender} which is used to add and remove
79      * {@link android.media.midi.MidiReceiver}s
80      * to the dispatcher's receiver list.
81      * @return the dispatcher's MidiSender
82      */
getSender()83     public MidiSender getSender() {
84         return mSender;
85     }
86 
87     @Override
onSend(byte[] msg, int offset, int count, long timestamp)88     public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
89        for (MidiReceiver receiver : mReceivers) {
90             try {
91                 receiver.send(msg, offset, count, timestamp);
92             } catch (IOException e) {
93                 // If the receiver fails we remove the receiver but do not propagate the exception.
94                 // Note that this may also happen if the client code stalls, and thus underlying
95                 // MidiInputPort.onSend has raised IOException for EAGAIN / EWOULDBLOCK error.
96                 mReceivers.remove(receiver);
97                 if (mFailureHandler != null) {
98                     mFailureHandler.onReceiverFailure(receiver, e);
99                 }
100             }
101         }
102     }
103 
104     @Override
onFlush()105     public void onFlush() throws IOException {
106        for (MidiReceiver receiver : mReceivers) {
107             try {
108                 receiver.flush();
109             } catch (IOException e) {
110                 // This is just a special case of 'send' thus handle in the same way.
111                 mReceivers.remove(receiver);
112                 if (mFailureHandler != null) {
113                     mFailureHandler.onReceiverFailure(receiver, e);
114                 }
115             }
116        }
117     }
118 }
119