diff options
5 files changed, 407 insertions, 14 deletions
diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java new file mode 100644 index 0000000..3fcee86 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.MojoException; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for bindings tests. + */ +public class BindingsTestUtils { + + /** + * {@link MessageReceiver} that records any message it receives. + */ + public static class RecordingMessageReceiver implements MessageReceiver { + + public final List<Message> messages = new ArrayList<Message>(); + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + messages.add(message); + return true; + } + } + + /** + * {@link Connector.ErrorHandler} that records any error it received. + */ + public static class CapturingErrorHandler implements Connector.ErrorHandler { + + public MojoException exception = null; + + /** + * @see Connector.ErrorHandler#onError(MojoException) + */ + @Override + public void onError(MojoException e) { + exception = e; + } + } + +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java new file mode 100644 index 0000000..57f2550 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java @@ -0,0 +1,108 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.test.suitebuilder.annotation.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.TestUtils; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiver; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Testing the {@link Connector} class. + */ +public class ConnectorTest extends MojoTestCase { + + private static final long RUN_LOOP_TIMEOUT_MS = 25; + + private static final int DATA_LENGTH = 1024; + + private MessagePipeHandle mHandle; + private Connector mConnector; + private Message mTestMessage; + private RecordingMessageReceiver mReceiver; + private CapturingErrorHandler mErrorHandler; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(); + mHandle = handles.first; + mConnector = new Connector(handles.second); + mReceiver = new RecordingMessageReceiver(); + mConnector.setIncomingMessageReceiver(mReceiver); + mErrorHandler = new CapturingErrorHandler(); + mConnector.setErrorHandler(mErrorHandler); + mConnector.start(); + mTestMessage = new Message(TestUtils.newRandomBuffer(DATA_LENGTH), new ArrayList<Handle>()); + assertNull(mErrorHandler.exception); + assertEquals(0, mReceiver.messages.size()); + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + mConnector.close(); + mHandle.close(); + super.tearDown(); + } + + /** + * Test sending a message through a {@link Connector}. + */ + @SmallTest + public void testSendingMessage() { + mConnector.accept(mTestMessage); + assertNull(mErrorHandler.exception); + ByteBuffer received = ByteBuffer.allocateDirect(DATA_LENGTH); + MessagePipeHandle.ReadMessageResult result = mHandle.readMessage(received, 0, + MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(DATA_LENGTH, result.getMessageSize()); + assertEquals(mTestMessage.buffer, received); + } + + /** + * Test receiving a message through a {@link Connector} + */ + @SmallTest + public void testReceivingMessage() { + mHandle.writeMessage(mTestMessage.buffer, new ArrayList<Handle>(), + MessagePipeHandle.WriteFlags.NONE); + nativeRunLoop(RUN_LOOP_TIMEOUT_MS); + assertNull(mErrorHandler.exception); + assertEquals(1, mReceiver.messages.size()); + Message received = mReceiver.messages.get(0); + assertEquals(0, received.handles.size()); + assertEquals(mTestMessage.buffer, received.buffer); + } + + /** + * Test receiving an error through a {@link Connector}. + */ + @SmallTest + public void testErrors() { + mHandle.close(); + nativeRunLoop(RUN_LOOP_TIMEOUT_MS); + assertNotNull(mErrorHandler.exception); + assertEquals(MojoResult.FAILED_PRECONDITION, mErrorHandler.exception.getMojoResult()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageTest.java index ee58979..9f518f5 100644 --- a/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageTest.java +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageTest.java @@ -8,6 +8,7 @@ import android.test.suitebuilder.annotation.SmallTest; import org.chromium.mojo.MojoTestCase; import org.chromium.mojo.TestUtils; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiver; import org.chromium.mojo.system.Core; import org.chromium.mojo.system.DataPipe; import org.chromium.mojo.system.Handle; @@ -27,20 +28,6 @@ import java.util.List; */ public class MessageTest extends MojoTestCase { - static class RecordingMessageReceiver implements MessageReceiver { - - public final List<Message> messages = new ArrayList<Message>(); - - /** - * @see MessageReceiver#accept(Message) - */ - @Override - public boolean accept(Message message) { - messages.add(message); - return true; - } - } - private static final int DATA_SIZE = 1024; private ByteBuffer mData; diff --git a/mojo/bindings/java/src/org/chromium/mojo/bindings/Connector.java b/mojo/bindings/java/src/org/chromium/mojo/bindings/Connector.java new file mode 100644 index 0000000..9c74fbd --- /dev/null +++ b/mojo/bindings/java/src/org/chromium/mojo/bindings/Connector.java @@ -0,0 +1,226 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.AsyncWaiter; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; + +/** + * A {@link Connector} owns a {@link MessagePipeHandle} and will send any received messages to the + * registered {@link MessageReceiver}. It also acts as a {@link MessageReceiver} and will send any + * message through the handle. + * <p> + * The method |start| must be called before the {@link Connector} will start listening to incoming + * messages. + */ +public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle> { + + /** + * An {@link ErrorHandler} is notified of error happening while using the message pipe. + */ + interface ErrorHandler { + public void onError(MojoException e); + } + + /** + * The callback that is notified when the state of the owned handle changes. + */ + private final AsyncWaiterCallback mAsyncWaiterCallback = new AsyncWaiterCallback(); + + /** + * The owned message pipe. + */ + private final MessagePipeHandle mMessagePipeHandle; + + /** + * A waiter which is notified when a new message is available on the owned message pipe. + */ + private final AsyncWaiter mAsyncWaiter; + + /** + * The {@link MessageReceiver} to which received messages are sent. + */ + private MessageReceiver mIncomingMessageReceiver; + + /** + * The Cancellable for the current wait. Is |null| when not currently waiting for new messages. + */ + private AsyncWaiter.Cancellable mCancellable; + + /** + * The error handler to notify of errors. + */ + private ErrorHandler mErrorHandler; + + /** + * Create a new connector over a |messagePipeHandle|. The created connector will use the default + * {@link AsyncWaiter} from the {@link Core} implementation of |messagePipeHandle|. + */ + public Connector(MessagePipeHandle messagePipeHandle) { + this(messagePipeHandle, getDefaultAsyncWaiterForMessagePipe(messagePipeHandle)); + } + + /** + * Create a new connector over a |messagePipeHandle| using the given {@link AsyncWaiter} to get + * notified of changes on the handle. + */ + public Connector(MessagePipeHandle messagePipeHandle, AsyncWaiter asyncWaiter) { + mCancellable = null; + mMessagePipeHandle = messagePipeHandle; + mAsyncWaiter = asyncWaiter; + } + + /** + * Set the {@link MessageReceiver} that will receive message from the owned message pipe. + */ + public void setIncomingMessageReceiver(MessageReceiver incomingMessageReceiver) { + mIncomingMessageReceiver = incomingMessageReceiver; + } + + /** + * Set the {@link ErrorHandler} that will be notified of errors on the owned message pipe. + */ + public void setErrorHandler(ErrorHandler errorHandler) { + mErrorHandler = errorHandler; + } + + /** + * Start listening for incoming messages. + */ + public void start() { + assert mCancellable == null; + registerAsyncWaiterForRead(); + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + try { + mMessagePipeHandle.writeMessage(message.buffer, message.handles, + MessagePipeHandle.WriteFlags.NONE); + return true; + } catch (MojoException e) { + onError(e); + return false; + } + } + + /** + * Pass the owned handle of the connector. After this, the connector is disconnected. It cannot + * accept new message and it isn't listening to the handle anymore. + * + * @see org.chromium.mojo.bindings.HandleOwner#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + cancelIfActive(); + return mMessagePipeHandle.pass(); + } + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + cancelIfActive(); + mMessagePipeHandle.close(); + } + + private static AsyncWaiter getDefaultAsyncWaiterForMessagePipe( + MessagePipeHandle messagePipeHandle) { + if (messagePipeHandle.getCore() != null) { + return messagePipeHandle.getCore().getDefaultAsyncWaiter(); + } else { + return null; + } + } + + private class AsyncWaiterCallback implements AsyncWaiter.Callback { + + /** + * @see org.chromium.mojo.system.AsyncWaiter.Callback#onResult(int) + */ + @Override + public void onResult(int result) { + Connector.this.onAsyncWaiterResult(result); + } + + /** + * @see org.chromium.mojo.system.AsyncWaiter.Callback#onError(MojoException) + */ + @Override + public void onError(MojoException exception) { + Connector.this.onError(exception); + } + + } + + /** + * @see org.chromium.mojo.system.AsyncWaiter.Callback#onResult(int) + */ + private void onAsyncWaiterResult(int result) { + mCancellable = null; + if (result == MojoResult.OK) { + readOutstandingMessages(); + } else { + onError(new MojoException(result)); + } + } + + private void onError(MojoException exception) { + mCancellable = null; + close(); + if (mErrorHandler != null) { + mErrorHandler.onError(exception); + } + } + + /** + * Register to be called back when a new message is available on the owned message pipe. + */ + private void registerAsyncWaiterForRead() { + assert mCancellable == null; + if (mAsyncWaiter != null) { + mCancellable = mAsyncWaiter.asyncWait(mMessagePipeHandle, Core.WaitFlags.READABLE, + Core.DEADLINE_INFINITE, mAsyncWaiterCallback); + } else { + onError(new MojoException(MojoResult.INVALID_ARGUMENT)); + } + } + + /** + * Read all available messages on the owned message pipe. + */ + private void readOutstandingMessages() { + int result; + do { + try { + result = Message.readAndDispatchMessage(mMessagePipeHandle, + mIncomingMessageReceiver); + } catch (MojoException e) { + onError(e); + return; + } + } while (result == MojoResult.OK); + if (result == MojoResult.SHOULD_WAIT) { + registerAsyncWaiterForRead(); + } else { + onError(new MojoException(result)); + } + } + + private void cancelIfActive() { + if (mCancellable != null) { + mCancellable.cancel(); + mCancellable = null; + } + } + +} diff --git a/mojo/bindings/java/src/org/chromium/mojo/bindings/HandleOwner.java b/mojo/bindings/java/src/org/chromium/mojo/bindings/HandleOwner.java new file mode 100644 index 0000000..7f1cfd9 --- /dev/null +++ b/mojo/bindings/java/src/org/chromium/mojo/bindings/HandleOwner.java @@ -0,0 +1,22 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.Handle; + +import java.io.Closeable; + +/** + * Describes a class that owns a handle. + * + * @param <H> The type of the owned handle. + */ +public interface HandleOwner<H extends Handle> extends Closeable { + + /** + * Pass the handle owned by this class. + */ + public H passHandle(); +} |