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