• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.mojo.bindings;
6 
7 import android.annotation.SuppressLint;
8 
9 import org.chromium.mojo.system.Core;
10 import org.chromium.mojo.system.MessagePipeHandle;
11 import org.chromium.mojo.system.Watcher;
12 
13 import java.util.HashMap;
14 import java.util.Map;
15 import java.util.concurrent.Executor;
16 
17 /**
18  * Implementation of {@link Router}.
19  */
20 @SuppressLint("UseSparseArrays")  // https://crbug.com/600699
21 public class RouterImpl implements Router {
22 
23     /**
24      * {@link MessageReceiver} used as the {@link Connector} callback.
25      */
26     private class HandleIncomingMessageThunk implements MessageReceiver {
27 
28         /**
29          * @see MessageReceiver#accept(Message)
30          */
31         @Override
accept(Message message)32         public boolean accept(Message message) {
33             return handleIncomingMessage(message);
34         }
35 
36         /**
37          * @see MessageReceiver#close()
38          */
39         @Override
close()40         public void close() {
41             handleConnectorClose();
42         }
43 
44     }
45 
46     /**
47      *
48      * {@link MessageReceiver} used to return responses to the caller.
49      */
50     class ResponderThunk implements MessageReceiver {
51         private boolean mAcceptWasInvoked;
52 
53         /**
54          * @see
55          * MessageReceiver#accept(Message)
56          */
57         @Override
accept(Message message)58         public boolean accept(Message message) {
59             mAcceptWasInvoked = true;
60             return RouterImpl.this.accept(message);
61         }
62 
63         /**
64          * @see MessageReceiver#close()
65          */
66         @Override
close()67         public void close() {
68             RouterImpl.this.close();
69         }
70 
71         @Override
finalize()72         protected void finalize() throws Throwable {
73             if (!mAcceptWasInvoked) {
74                 // We close the pipe here as a way of signaling to the calling application that an
75                 // error condition occurred. Without this the calling application would have no
76                 // way of knowing it should stop waiting for a response.
77                 RouterImpl.this.closeOnHandleThread();
78             }
79             super.finalize();
80         }
81     }
82 
83     /**
84      * The {@link Connector} which is connected to the handle.
85      */
86     private final Connector mConnector;
87 
88     /**
89      * The {@link MessageReceiverWithResponder} that will consume the messages received from the
90      * pipe.
91      */
92     private MessageReceiverWithResponder mIncomingMessageReceiver;
93 
94     /**
95      * The next id to use for a request id which needs a response. It is auto-incremented.
96      */
97     private long mNextRequestId = 1;
98 
99     /**
100      * The map from request ids to {@link MessageReceiver} of request currently in flight.
101      */
102     private Map<Long, MessageReceiver> mResponders = new HashMap<Long, MessageReceiver>();
103 
104     /**
105      * An Executor that will run on the thread associated with the MessagePipe to which
106      * this Router is bound. This may be {@code Null} if the MessagePipeHandle passed
107      * in to the constructor is not valid.
108      */
109     private final Executor mExecutor;
110 
111     /**
112      * Constructor that will use the default {@link Watcher}.
113      *
114      * @param messagePipeHandle The {@link MessagePipeHandle} to route message for.
115      */
RouterImpl(MessagePipeHandle messagePipeHandle)116     public RouterImpl(MessagePipeHandle messagePipeHandle) {
117         this(messagePipeHandle, BindingsHelper.getWatcherForHandle(messagePipeHandle));
118     }
119 
120     /**
121      * Constructor.
122      *
123      * @param messagePipeHandle The {@link MessagePipeHandle} to route message for.
124      * @param watcher the {@link Watcher} to use to get notification of new messages on the
125      *            handle.
126      */
RouterImpl(MessagePipeHandle messagePipeHandle, Watcher watcher)127     public RouterImpl(MessagePipeHandle messagePipeHandle, Watcher watcher) {
128         mConnector = new Connector(messagePipeHandle, watcher);
129         mConnector.setIncomingMessageReceiver(new HandleIncomingMessageThunk());
130         Core core = messagePipeHandle.getCore();
131         if (core != null) {
132             mExecutor = ExecutorFactory.getExecutorForCurrentThread(core);
133         } else {
134             mExecutor = null;
135         }
136     }
137 
138     /**
139      * @see org.chromium.mojo.bindings.Router#start()
140      */
141     @Override
start()142     public void start() {
143         mConnector.start();
144     }
145 
146     /**
147      * @see Router#setIncomingMessageReceiver(MessageReceiverWithResponder)
148      */
149     @Override
setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver)150     public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver) {
151         this.mIncomingMessageReceiver = incomingMessageReceiver;
152     }
153 
154     /**
155      * @see MessageReceiver#accept(Message)
156      */
157     @Override
accept(Message message)158     public boolean accept(Message message) {
159         // A message without responder is directly forwarded to the connector.
160         return mConnector.accept(message);
161     }
162 
163     /**
164      * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver)
165      */
166     @Override
acceptWithResponder(Message message, MessageReceiver responder)167     public boolean acceptWithResponder(Message message, MessageReceiver responder) {
168         // The message must have a header.
169         ServiceMessage messageWithHeader = message.asServiceMessage();
170         // Checking the message expects a response.
171         assert messageWithHeader.getHeader().hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG);
172 
173         // Compute a request id for being able to route the response.
174         long requestId = mNextRequestId++;
175         // Reserve 0 in case we want it to convey special meaning in the future.
176         if (requestId == 0) {
177             requestId = mNextRequestId++;
178         }
179         if (mResponders.containsKey(requestId)) {
180             throw new IllegalStateException("Unable to find a new request identifier.");
181         }
182         messageWithHeader.setRequestId(requestId);
183         if (!mConnector.accept(messageWithHeader)) {
184             return false;
185         }
186         // Only keep the responder is the message has been accepted.
187         mResponders.put(requestId, responder);
188         return true;
189     }
190 
191     /**
192      * @see org.chromium.mojo.bindings.HandleOwner#passHandle()
193      */
194     @Override
passHandle()195     public MessagePipeHandle passHandle() {
196         return mConnector.passHandle();
197     }
198 
199     /**
200      * @see java.io.Closeable#close()
201      */
202     @Override
close()203     public void close() {
204         mConnector.close();
205     }
206 
207     /**
208      * @see Router#setErrorHandler(ConnectionErrorHandler)
209      */
210     @Override
setErrorHandler(ConnectionErrorHandler errorHandler)211     public void setErrorHandler(ConnectionErrorHandler errorHandler) {
212         mConnector.setErrorHandler(errorHandler);
213     }
214 
215     /**
216      * Receive a message from the connector. Returns |true| if the message has been handled.
217      */
handleIncomingMessage(Message message)218     private boolean handleIncomingMessage(Message message) {
219         MessageHeader header = message.asServiceMessage().getHeader();
220         if (header.hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)) {
221             if (mIncomingMessageReceiver != null) {
222                 return mIncomingMessageReceiver.acceptWithResponder(message, new ResponderThunk());
223             }
224             // If we receive a request expecting a response when the client is not
225             // listening, then we have no choice but to tear down the pipe.
226             close();
227             return false;
228         } else if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) {
229             long requestId = header.getRequestId();
230             MessageReceiver responder = mResponders.get(requestId);
231             if (responder == null) {
232                 return false;
233             }
234             mResponders.remove(requestId);
235             return responder.accept(message);
236         } else {
237             if (mIncomingMessageReceiver != null) {
238                 return mIncomingMessageReceiver.accept(message);
239             }
240             // OK to drop the message.
241         }
242         return false;
243     }
244 
handleConnectorClose()245     private void handleConnectorClose() {
246         if (mIncomingMessageReceiver != null) {
247             mIncomingMessageReceiver.close();
248         }
249     }
250 
251     /**
252      * Invokes {@link #close()} asynchronously on the thread associated with
253      * this Router's Handle. If this Router was constructed with an invalid
254      * handle then this method does nothing.
255      */
closeOnHandleThread()256     private void closeOnHandleThread() {
257         if (mExecutor != null) {
258             mExecutor.execute(new Runnable() {
259 
260                 @Override
261                 public void run() {
262                     close();
263                 }
264             });
265         }
266     }
267 }
268