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
17package com.android.internal.midi;
18
19import android.media.midi.MidiReceiver;
20import android.media.midi.MidiSender;
21
22import java.io.IOException;
23import 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 */
33public 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 {
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
61    public MidiDispatcher() {
62        this(null);
63    }
64
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     */
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     */
83    public MidiSender getSender() {
84        return mSender;
85    }
86
87    @Override
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
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