diff options
Diffstat (limited to 'mojo/android')
47 files changed, 5587 insertions, 0 deletions
diff --git a/mojo/android/BUILD.gn b/mojo/android/BUILD.gn new file mode 100644 index 0000000000..1a8cdbdb3c --- /dev/null +++ b/mojo/android/BUILD.gn @@ -0,0 +1,155 @@ +# 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. + +import("//build/config/android/rules.gni") + +group("android") { + testonly = true + deps = [ + ":mojo_javatests", + ":mojo_test_apk", + ":system_java", + ] +} + +generate_jni("jni_headers") { + sources = [ + "javatests/src/org/chromium/mojo/MojoTestCase.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java", + ] + public_deps = [ + ":system_java_jni_headers", + ] + + jni_package = "mojo" +} + +generate_jni("system_java_jni_headers") { + sources = [ + "system/src/org/chromium/mojo/system/impl/BaseRunLoop.java", + "system/src/org/chromium/mojo/system/impl/CoreImpl.java", + "system/src/org/chromium/mojo/system/impl/WatcherImpl.java", + ] + + jni_package = "mojo" +} + +source_set("libsystem_java") { + sources = [ + "system/base_run_loop.cc", + "system/base_run_loop.h", + "system/core_impl.cc", + "system/core_impl.h", + "system/watcher_impl.cc", + "system/watcher_impl.h", + ] + + deps = [ + ":system_java_jni_headers", + "//base", + "//mojo/public/c/system", + "//mojo/public/cpp/system", + ] +} + +android_library("system_java") { + java_files = [ + "system/src/org/chromium/mojo/system/impl/BaseRunLoop.java", + "system/src/org/chromium/mojo/system/impl/CoreImpl.java", + "system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/HandleBase.java", + "system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/WatcherImpl.java", + ] + + deps = [ + "//base:base_java", + "//mojo/public/java:system_java", + ] +} + +android_library("mojo_javatests") { + testonly = true + java_files = [ + "javatests/src/org/chromium/mojo/HandleMock.java", + "javatests/src/org/chromium/mojo/MojoTestCase.java", + "javatests/src/org/chromium/mojo/TestUtils.java", + "javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java", + "javatests/src/org/chromium/mojo/bindings/BindingsTest.java", + "javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java", + "javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java", + "javatests/src/org/chromium/mojo/bindings/CallbacksTest.java", + "javatests/src/org/chromium/mojo/bindings/ConnectorTest.java", + "javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java", + "javatests/src/org/chromium/mojo/bindings/InterfacesTest.java", + "javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java", + "javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java", + "javatests/src/org/chromium/mojo/bindings/RouterTest.java", + "javatests/src/org/chromium/mojo/bindings/SerializationTest.java", + "javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTest.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java", + "javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java", + "javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java", + ] + + deps = [ + ":system_java", + "//base:base_java", + "//base:base_java_test_support", + "//mojo/public/interfaces/bindings/tests:test_interfaces_java", + "//mojo/public/interfaces/bindings/tests:test_mojom_import2_java", + "//mojo/public/interfaces/bindings/tests:test_mojom_import_java", + "//mojo/public/java:bindings_java", + "//mojo/public/java:system_java", + "//third_party/android_support_test_runner:runner_java", + ] + + data = [ + "//mojo/public/interfaces/bindings/tests/data/validation/", + ] +} + +shared_library("mojo_java_unittests") { + testonly = true + + sources = [ + "javatests/init_library.cc", + "javatests/mojo_test_case.cc", + "javatests/mojo_test_case.h", + "javatests/validation_test_util.cc", + "javatests/validation_test_util.h", + ] + + deps = [ + ":jni_headers", + ":libsystem_java", + ":system_java_jni_headers", + "//base", + "//base/test:test_support", + "//build/config/sanitizers:deps", + "//mojo/edk/system", + "//mojo/public/cpp/bindings/tests:mojo_public_bindings_test_utils", + "//mojo/public/cpp/test_support:test_utils", + ] + defines = [ "UNIT_TEST" ] +} + +instrumentation_test_apk("mojo_test_apk") { + deps = [ + ":mojo_javatests", + ":system_java", + "//base:base_java", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//mojo/public/java:bindings_java", + "//third_party/android_support_test_runner:runner_java", + ] + shared_libraries = [ ":mojo_java_unittests" ] + apk_name = "MojoTest" + android_manifest = "javatests/AndroidManifest.xml" +} diff --git a/mojo/android/DEPS b/mojo/android/DEPS new file mode 100644 index 0000000000..c80012b562 --- /dev/null +++ b/mojo/android/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+jni", +] diff --git a/mojo/android/javatests/AndroidManifest.xml b/mojo/android/javatests/AndroidManifest.xml new file mode 100644 index 0000000000..32a3927662 --- /dev/null +++ b/mojo/android/javatests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- Copyright (c) 2012 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 name must be unique so suffix with "tests" so package loader + doesn't ignore this. --> + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="org.chromium.mojo.tests"> + + <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" /> + + <uses-permission android:name="android.permission.INJECT_EVENTS" + tools:ignore="ProtectedPermissions"/> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.RUN_INSTRUMENTATION" /> + + <!-- We add an application tag here just so that we can indicate that this + package needs to link against the android.test library, which is + needed when building test cases. --> + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="org.chromium.mojo.tests" + android:label="Tests for org.chromium.mojo"/> + +</manifest> diff --git a/mojo/android/javatests/DEPS b/mojo/android/javatests/DEPS new file mode 100644 index 0000000000..78cf465ca0 --- /dev/null +++ b/mojo/android/javatests/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + # out should be allowed by default, but bots are failing on this. + "+out", +] diff --git a/mojo/android/javatests/apk/.empty b/mojo/android/javatests/apk/.empty new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/android/javatests/apk/.empty diff --git a/mojo/android/javatests/init_library.cc b/mojo/android/javatests/init_library.cc new file mode 100644 index 0000000000..9e1a593011 --- /dev/null +++ b/mojo/android/javatests/init_library.cc @@ -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. + +#include "base/android/base_jni_onload.h" +#include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/library_loader/library_loader_hooks.h" +#include "base/bind.h" +#include "mojo/android/javatests/mojo_test_case.h" +#include "mojo/android/javatests/validation_test_util.h" +#include "mojo/android/system/core_impl.h" +#include "mojo/android/system/watcher_impl.h" +#include "mojo/edk/embedder/embedder.h" + +namespace { + +base::android::RegistrationMethod kMojoRegisteredMethods[] = { + {"CoreImpl", mojo::android::RegisterCoreImpl}, + {"MojoTestCase", mojo::android::RegisterMojoTestCase}, + {"ValidationTestUtil", mojo::android::RegisterValidationTestUtil}, + {"WatcherImpl", mojo::android::RegisterWatcherImpl}, +}; + +bool RegisterJNI(JNIEnv* env) { + return base::android::RegisterJni(env) && + RegisterNativeMethods(env, kMojoRegisteredMethods, + arraysize(kMojoRegisteredMethods)); +} + +bool NativeInit() { + if (!base::android::OnJNIOnLoadInit()) + return false; + + mojo::edk::Init(); + return true; +} + +} // namespace + +JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + base::android::InitVM(vm); + JNIEnv* env = base::android::AttachCurrentThread(); + if (!base::android::OnJNIOnLoadRegisterJNI(env) || !RegisterJNI(env) || + !NativeInit()) { + return -1; + } + return JNI_VERSION_1_4; +} diff --git a/mojo/android/javatests/mojo_test_case.cc b/mojo/android/javatests/mojo_test_case.cc new file mode 100644 index 0000000000..fc59009bd4 --- /dev/null +++ b/mojo/android/javatests/mojo_test_case.cc @@ -0,0 +1,70 @@ +// 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. + +#include "mojo/android/javatests/mojo_test_case.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/at_exit.h" +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/test/test_support_android.h" +#include "base/threading/thread_task_runner_handle.h" +#include "jni/MojoTestCase_jni.h" + +using base::android::JavaParamRef; + +namespace { + +struct TestEnvironment { + TestEnvironment() {} + + base::ShadowingAtExitManager at_exit; + base::MessageLoop message_loop; +}; + +} // namespace + +namespace mojo { +namespace android { + +static void Init(JNIEnv* env, const JavaParamRef<jobject>& jcaller) { + base::InitAndroidTestMessageLoop(); +} + +static jlong SetupTestEnvironment(JNIEnv* env, + const JavaParamRef<jobject>& jcaller) { + return reinterpret_cast<intptr_t>(new TestEnvironment()); +} + +static void TearDownTestEnvironment(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jlong test_environment) { + delete reinterpret_cast<TestEnvironment*>(test_environment); +} + +static void RunLoop(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jlong timeout_ms) { + base::RunLoop run_loop; + if (timeout_ms) { + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), + base::TimeDelta::FromMilliseconds(timeout_ms)); + run_loop.Run(); + } else { + run_loop.RunUntilIdle(); + } +} + +bool RegisterMojoTestCase(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/javatests/mojo_test_case.h b/mojo/android/javatests/mojo_test_case.h new file mode 100644 index 0000000000..2ce342848e --- /dev/null +++ b/mojo/android/javatests/mojo_test_case.h @@ -0,0 +1,20 @@ +// 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. + +#ifndef MOJO_ANDROID_JAVATESTS_CORE_TEST_H_ +#define MOJO_ANDROID_JAVATESTS_CORE_TEST_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterMojoTestCase(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_SYSTEM_ANDROID_JAVATESTS_CORE_TEST_H_ diff --git a/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java new file mode 100644 index 0000000000..1f8de94500 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.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; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.UntypedHandle; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * A mock handle, that does nothing. + */ +public class HandleMock implements UntypedHandle, MessagePipeHandle, + ProducerHandle, ConsumerHandle, SharedBufferHandle { + + /** + * @see Handle#close() + */ + @Override + public void close() { + // Do nothing. + } + + /** + * @see Handle#querySignalsState() + */ + @Override + public HandleSignalsState querySignalsState() { + return null; + } + + /** + * @see Handle#isValid() + */ + @Override + public boolean isValid() { + return true; + } + + /** + * @see Handle#toUntypedHandle() + */ + @Override + public UntypedHandle toUntypedHandle() { + return this; + } + + /** + * @see org.chromium.mojo.system.Handle#getCore() + */ + @Override + public Core getCore() { + return CoreImpl.getInstance(); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#pass() + */ + @Override + public HandleMock pass() { + return this; + } + + /** + * @see Handle#releaseNativeHandle() + */ + @Override + public int releaseNativeHandle() { + return 0; + } + + /** + * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags) + */ + @Override + public int discardData(int numBytes, DataPipe.ReadFlags flags) { + // Do nothing. + return 0; + } + + /** + * @see ConsumerHandle#readData(java.nio.ByteBuffer, DataPipe.ReadFlags) + */ + @Override + public ResultAnd<Integer> readData(ByteBuffer elements, DataPipe.ReadFlags flags) { + // Do nothing. + return new ResultAnd<Integer>(MojoResult.OK, 0); + } + + /** + * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags) + */ + @Override + public ByteBuffer beginReadData(int numBytes, + DataPipe.ReadFlags flags) { + // Do nothing. + return null; + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + @Override + public void endReadData(int numBytesRead) { + // Do nothing. + } + + /** + * @see ProducerHandle#writeData(java.nio.ByteBuffer, DataPipe.WriteFlags) + */ + @Override + public ResultAnd<Integer> writeData(ByteBuffer elements, DataPipe.WriteFlags flags) { + // Do nothing. + return new ResultAnd<Integer>(MojoResult.OK, 0); + } + + /** + * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags) + */ + @Override + public ByteBuffer beginWriteData(int numBytes, + DataPipe.WriteFlags flags) { + // Do nothing. + return null; + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + @Override + public void endWriteData(int numBytesWritten) { + // Do nothing. + } + + /** + * @see MessagePipeHandle#writeMessage(java.nio.ByteBuffer, java.util.List, + * MessagePipeHandle.WriteFlags) + */ + @Override + public void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, + WriteFlags flags) { + // Do nothing. + } + + /** + * @see MessagePipeHandle#readMessage(java.nio.ByteBuffer, int, MessagePipeHandle.ReadFlags) + */ + @Override + public ResultAnd<ReadMessageResult> readMessage( + ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) { + // Do nothing. + return new ResultAnd<ReadMessageResult>(MojoResult.OK, new ReadMessageResult()); + } + + /** + * @see UntypedHandle#toMessagePipeHandle() + */ + @Override + public MessagePipeHandle toMessagePipeHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeConsumerHandle() + */ + @Override + public ConsumerHandle toDataPipeConsumerHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeProducerHandle() + */ + @Override + public ProducerHandle toDataPipeProducerHandle() { + return this; + } + + /** + * @see UntypedHandle#toSharedBufferHandle() + */ + @Override + public SharedBufferHandle toSharedBufferHandle() { + return this; + } + + /** + * @see SharedBufferHandle#duplicate(SharedBufferHandle.DuplicateOptions) + */ + @Override + public SharedBufferHandle duplicate(DuplicateOptions options) { + // Do nothing. + return null; + } + + /** + * @see SharedBufferHandle#map(long, long, SharedBufferHandle.MapFlags) + */ + @Override + public ByteBuffer map(long offset, long numBytes, MapFlags flags) { + // Do nothing. + return null; + } + + /** + * @see SharedBufferHandle#unmap(java.nio.ByteBuffer) + */ + @Override + public void unmap(ByteBuffer buffer) { + // Do nothing. + } + +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java b/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java new file mode 100644 index 0000000000..f4d7ab7236 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java @@ -0,0 +1,66 @@ +// 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; + +import android.test.InstrumentationTestCase; + +import org.chromium.base.ContextUtils; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.library_loader.LibraryProcessType; + +/** + * Base class to test mojo. Setup the environment. + */ +@JNINamespace("mojo::android") +public class MojoTestCase extends InstrumentationTestCase { + + private long mTestEnvironmentPointer; + + /** + * @see junit.framework.TestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + ContextUtils.initApplicationContext( + getInstrumentation().getTargetContext().getApplicationContext()); + LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER).ensureInitialized(); + nativeInit(); + mTestEnvironmentPointer = nativeSetupTestEnvironment(); + } + + /** + * @see android.test.InstrumentationTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + nativeTearDownTestEnvironment(mTestEnvironmentPointer); + super.tearDown(); + } + + /** + * Runs the run loop for the given time. + */ + protected void runLoop(long timeoutMS) { + nativeRunLoop(timeoutMS); + } + + /** + * Runs the run loop until no handle or task are immediately available. + */ + protected void runLoopUntilIdle() { + nativeRunLoop(0); + } + + private native void nativeInit(); + + private native long nativeSetupTestEnvironment(); + + private native void nativeTearDownTestEnvironment(long testEnvironment); + + private native void nativeRunLoop(long timeoutMS); + +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java b/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java new file mode 100644 index 0000000000..d10d0d7558 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java @@ -0,0 +1,32 @@ +// 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; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Random; + +/** + * Utilities methods for tests. + */ +public final class TestUtils { + + private static final Random RANDOM = new Random(); + + /** + * Returns a new direct ByteBuffer of the given size with random (but reproducible) data. + */ + public static ByteBuffer newRandomBuffer(int size) { + byte bytes[] = new byte[size]; + RANDOM.setSeed(size); + RANDOM.nextBytes(bytes); + ByteBuffer data = ByteBuffer.allocateDirect(size); + data.order(ByteOrder.LITTLE_ENDIAN); + data.put(bytes); + data.flip(); + return data; + } + +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java new file mode 100644 index 0000000000..38bd3482e4 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java @@ -0,0 +1,55 @@ +// 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.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import java.nio.charset.Charset; + +/** + * Testing {@link BindingsHelper}. + */ +public class BindingsHelperTest extends TestCase { + + /** + * Testing {@link BindingsHelper#utf8StringSizeInBytes(String)}. + */ + @SmallTest + public void testUTF8StringLength() { + String[] stringsToTest = { + "", + "a", + "hello world", + "éléphant", + "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕", + "你午饭想吃什么", + "你午饭想吃什么\0éléphant", + }; + for (String s : stringsToTest) { + assertEquals(s.getBytes(Charset.forName("utf8")).length, + BindingsHelper.utf8StringSizeInBytes(s)); + } + assertEquals(1, BindingsHelper.utf8StringSizeInBytes("\0")); + String s = new StringBuilder().appendCodePoint(0x0).appendCodePoint(0x80) + .appendCodePoint(0x800).appendCodePoint(0x10000).toString(); + assertEquals(10, BindingsHelper.utf8StringSizeInBytes(s)); + assertEquals(10, s.getBytes(Charset.forName("utf8")).length); + } + + /** + * Testing {@link BindingsHelper#align(int)}. + */ + @SmallTest + public void testAlign() { + for (int i = 0; i < 3 * BindingsHelper.ALIGNMENT; ++i) { + int j = BindingsHelper.align(i); + assertTrue(j >= i); + assertTrue(j % BindingsHelper.ALIGNMENT == 0); + assertTrue(j - i < BindingsHelper.ALIGNMENT); + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java new file mode 100644 index 0000000000..d280c774c6 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java @@ -0,0 +1,241 @@ +// 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.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.bindings.test.mojom.imported.Color; +import org.chromium.mojo.bindings.test.mojom.imported.Point; +import org.chromium.mojo.bindings.test.mojom.imported.Shape; +import org.chromium.mojo.bindings.test.mojom.imported.Thing; +import org.chromium.mojo.bindings.test.mojom.sample.Bar; +import org.chromium.mojo.bindings.test.mojom.sample.Bar.Type; +import org.chromium.mojo.bindings.test.mojom.sample.DefaultsTest; +import org.chromium.mojo.bindings.test.mojom.sample.Enum; +import org.chromium.mojo.bindings.test.mojom.sample.Foo; +import org.chromium.mojo.bindings.test.mojom.sample.InterfaceConstants; +import org.chromium.mojo.bindings.test.mojom.sample.SampleServiceConstants; +import org.chromium.mojo.bindings.test.mojom.test_structs.EmptyStruct; +import org.chromium.mojo.bindings.test.mojom.test_structs.Rect; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.MessagePipeHandle; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +/** + * Testing generated classes and associated features. + */ +public class BindingsTest extends TestCase { + + /** + * Create a new typical Bar instance. + */ + private static Bar newBar() { + Bar bar = new Bar(); + bar.alpha = (byte) 0x01; + bar.beta = (byte) 0x02; + bar.gamma = (byte) 0x03; + bar.type = Type.BOTH; + return bar; + } + + /** + * Create a new typical Foo instance. + */ + private static Foo createFoo() { + Foo foo = new Foo(); + foo.name = "HELLO WORLD"; + foo.arrayOfArrayOfBools = new boolean[][] { + { true, false, true }, { }, { }, { false }, { true } }; + foo.bar = newBar(); + foo.a = true; + foo.c = true; + foo.data = new byte[] { 0x01, 0x02, 0x03 }; + foo.extraBars = new Bar[] { newBar(), newBar() }; + String[][][] strings = new String[3][2][1]; + for (int i0 = 0; i0 < strings.length; ++i0) { + for (int i1 = 0; i1 < strings[i0].length; ++i1) { + for (int i2 = 0; i2 < strings[i0][i1].length; ++i2) { + strings[i0][i1][i2] = "Hello(" + i0 + ", " + i1 + ", " + i2 + ")"; + } + } + } + foo.multiArrayOfStrings = strings; + ConsumerHandle[] inputStreams = new ConsumerHandle[5]; + for (int i = 0; i < inputStreams.length; ++i) { + inputStreams[i] = new HandleMock(); + } + foo.inputStreams = inputStreams; + ProducerHandle[] outputStreams = new ProducerHandle[3]; + for (int i = 0; i < outputStreams.length; ++i) { + outputStreams[i] = new HandleMock(); + } + foo.outputStreams = outputStreams; + foo.source = new HandleMock(); + return foo; + } + + private static Rect createRect(int x, int y, int width, int height) { + Rect rect = new Rect(); + rect.x = x; + rect.y = y; + rect.width = width; + rect.height = height; + return rect; + } + + private static <T> void checkConstantField( + Field field, Class<T> expectedClass, T value) throws IllegalAccessException { + assertEquals(expectedClass, field.getType()); + assertEquals(Modifier.FINAL, field.getModifiers() & Modifier.FINAL); + assertEquals(Modifier.STATIC, field.getModifiers() & Modifier.STATIC); + assertEquals(value, field.get(null)); + } + + private static <T> void checkField(Field field, Class<T> expectedClass, + Object object, T value) throws IllegalArgumentException, IllegalAccessException { + assertEquals(expectedClass, field.getType()); + assertEquals(0, field.getModifiers() & Modifier.FINAL); + assertEquals(0, field.getModifiers() & Modifier.STATIC); + assertEquals(value, field.get(object)); + } + + /** + * Testing constants are correctly generated. + */ + @SmallTest + public void testConstants() throws NoSuchFieldException, SecurityException, + IllegalAccessException { + checkConstantField(SampleServiceConstants.class.getField("TWELVE"), byte.class, (byte) 12); + checkConstantField(InterfaceConstants.class.getField("LONG"), long.class, 4405L); + } + + /** + * Testing enums are correctly generated. + */ + @SmallTest + public void testEnums() throws NoSuchFieldException, SecurityException, + IllegalAccessException { + checkConstantField(Color.class.getField("RED"), int.class, 0); + checkConstantField(Color.class.getField("BLACK"), int.class, 1); + + checkConstantField(Enum.class.getField("VALUE"), int.class, 0); + + checkConstantField(Shape.class.getField("RECTANGLE"), int.class, 1); + checkConstantField(Shape.class.getField("CIRCLE"), int.class, 2); + checkConstantField(Shape.class.getField("TRIANGLE"), int.class, 3); + } + + /** + * Testing default values on structs. + * + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + @SmallTest + public void testStructDefaults() throws NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException { + // Check default values. + DefaultsTest test = new DefaultsTest(); + + checkField(DefaultsTest.class.getField("a0"), byte.class, test, (byte) -12); + checkField(DefaultsTest.class.getField("a1"), byte.class, test, (byte) 12); + checkField(DefaultsTest.class.getField("a2"), short.class, test, (short) 1234); + checkField(DefaultsTest.class.getField("a3"), short.class, test, (short) 34567); + checkField(DefaultsTest.class.getField("a4"), int.class, test, 123456); + checkField(DefaultsTest.class.getField("a5"), int.class, test, (int) 3456789012L); + checkField(DefaultsTest.class.getField("a6"), long.class, test, -111111111111L); + // -8446744073709551617 == 9999999999999999999 - 2 ^ 64. + checkField(DefaultsTest.class.getField("a7"), long.class, test, -8446744073709551617L); + checkField(DefaultsTest.class.getField("a8"), int.class, test, 0x12345); + checkField(DefaultsTest.class.getField("a9"), int.class, test, -0x12345); + checkField(DefaultsTest.class.getField("a10"), int.class, test, 1234); + checkField(DefaultsTest.class.getField("a11"), boolean.class, test, true); + checkField(DefaultsTest.class.getField("a12"), boolean.class, test, false); + checkField(DefaultsTest.class.getField("a13"), float.class, test, (float) 123.25); + checkField(DefaultsTest.class.getField("a14"), double.class, test, 1234567890.123); + checkField(DefaultsTest.class.getField("a15"), double.class, test, 1E10); + checkField(DefaultsTest.class.getField("a16"), double.class, test, -1.2E+20); + checkField(DefaultsTest.class.getField("a17"), double.class, test, +1.23E-20); + checkField(DefaultsTest.class.getField("a18"), byte[].class, test, null); + checkField(DefaultsTest.class.getField("a19"), String.class, test, null); + checkField(DefaultsTest.class.getField("a20"), int.class, test, Bar.Type.BOTH); + checkField(DefaultsTest.class.getField("a21"), Point.class, test, null); + + assertNotNull(test.a22); + checkField(DefaultsTest.class.getField("a22"), Thing.class, test, test.a22); + checkField(DefaultsTest.class.getField("a23"), long.class, test, -1L); + checkField(DefaultsTest.class.getField("a24"), long.class, test, 0x123456789L); + checkField(DefaultsTest.class.getField("a25"), long.class, test, -0x123456789L); + } + + /** + * Testing generation of the Foo class. + * + * @throws IllegalAccessException + */ + @SmallTest + public void testFooGeneration() throws NoSuchFieldException, SecurityException, + IllegalAccessException { + // Checking Foo constants. + checkConstantField(Foo.class.getField("FOOBY"), String.class, "Fooby"); + + // Checking Foo default values. + Foo foo = new Foo(); + checkField(Foo.class.getField("name"), String.class, foo, Foo.FOOBY); + + assertNotNull(foo.source); + assertFalse(foo.source.isValid()); + checkField(Foo.class.getField("source"), MessagePipeHandle.class, foo, foo.source); + } + + /** + * Testing serialization of the Foo class. + */ + @SmallTest + public void testFooSerialization() { + // Checking serialization and deserialization of a Foo object. + Foo typicalFoo = createFoo(); + Message serializedFoo = typicalFoo.serialize(null); + Foo deserializedFoo = Foo.deserialize(serializedFoo); + assertEquals(typicalFoo, deserializedFoo); + } + + /** + * Testing serialization of the EmptyStruct class. + */ + @SmallTest + public void testEmptyStructSerialization() { + // Checking serialization and deserialization of a EmptyStruct object. + Message serializedStruct = new EmptyStruct().serialize(null); + EmptyStruct emptyStruct = EmptyStruct.deserialize(serializedStruct); + assertNotNull(emptyStruct); + } + + // In testing maps we want to make sure that the key used when inserting an + // item the key used when looking it up again are different objects. Java + // has default implementations of equals and hashCode that use reference + // equality and hashing, respectively, and that's not what we want for our + // mojom values. + @SmallTest + public void testHashMapStructKey() { + Map<Rect, Integer> map = new HashMap<>(); + map.put(createRect(1, 2, 3, 4), 123); + + Rect key = createRect(1, 2, 3, 4); + assertNotNull(map.get(key)); + assertEquals(123, map.get(key).intValue()); + + map.remove(key); + assertTrue(map.isEmpty()); + } +} 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 0000000000..5554f805a7 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.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 org.chromium.mojo.TestUtils; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.Closeable; +import java.nio.ByteBuffer; +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 extends SideEffectFreeCloseable + 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 MessageReceiverWithResponder} that records any message it receives. + */ + public static class RecordingMessageReceiverWithResponder extends RecordingMessageReceiver + implements MessageReceiverWithResponder { + + public final List<Pair<Message, MessageReceiver>> messagesWithReceivers = + new ArrayList<Pair<Message, MessageReceiver>>(); + + /** + * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver) + */ + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + messagesWithReceivers.add(Pair.create(message, responder)); + return true; + } + } + + /** + * {@link ConnectionErrorHandler} that records any error it received. + */ + public static class CapturingErrorHandler implements ConnectionErrorHandler { + + private MojoException mLastMojoException = null; + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + mLastMojoException = e; + } + + /** + * Returns the last recorded exception. + */ + public MojoException getLastMojoException() { + return mLastMojoException; + } + + } + + /** + * Creates a new valid {@link Message}. The message will have a valid header. + */ + public static Message newRandomMessage(int size) { + assert size > 16; + ByteBuffer message = TestUtils.newRandomBuffer(size); + int[] headerAsInts = {16, 2, 0, 0}; + for (int i = 0; i < 4; ++i) { + message.putInt(4 * i, headerAsInts[i]); + } + message.position(0); + return new Message(message, new ArrayList<Handle>()); + } + + public static <I extends Interface, P extends Interface.Proxy> P newProxyOverPipe( + Interface.Manager<I, P> manager, I impl, List<Closeable> toClose) { + Pair<MessagePipeHandle, MessagePipeHandle> handles = + CoreImpl.getInstance().createMessagePipe(null); + P proxy = manager.attachProxy(handles.first, 0); + toClose.add(proxy); + manager.bind(impl, handles.second); + return proxy; + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java new file mode 100644 index 0000000000..eea92ab7b8 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java @@ -0,0 +1,211 @@ +// Copyright 2015 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.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStruct; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV0; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV1; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV3; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV5; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV7; +import org.chromium.mojo.bindings.test.mojom.test_structs.Rect; +import org.chromium.mojo.system.impl.CoreImpl; + +/** + * Testing generated classes with the [MinVersion] annotation. Struct in this test are from: + * mojo/public/interfaces/bindings/tests/rect.mojom and + * mojo/public/interfaces/bindings/tests/test_structs.mojom + */ +public class BindingsVersioningTest extends MojoTestCase { + private static Rect newRect(int factor) { + Rect rect = new Rect(); + rect.x = factor; + rect.y = 2 * factor; + rect.width = 10 * factor; + rect.height = 20 * factor; + return rect; + } + + private static MultiVersionStruct newStruct() { + MultiVersionStruct struct = new MultiVersionStruct(); + struct.fInt32 = 123; + struct.fRect = newRect(5); + struct.fString = "hello"; + struct.fArray = new byte[] {10, 9, 8}; + struct.fBool = true; + struct.fInt16 = 256; + return struct; + } + + /** + * Testing serializing old struct version to newer one. + */ + @SmallTest + public void testOldToNew() { + { + MultiVersionStructV0 v0 = new MultiVersionStructV0(); + v0.fInt32 = 123; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v0.serialize(null)); + assertEquals(expected, output); + assertEquals(0, v0.getVersion()); + assertEquals(0, output.getVersion()); + } + + { + MultiVersionStructV1 v1 = new MultiVersionStructV1(); + v1.fInt32 = 123; + v1.fRect = newRect(5); + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + + MultiVersionStruct output = MultiVersionStruct.deserialize(v1.serialize(null)); + assertEquals(expected, output); + assertEquals(1, v1.getVersion()); + assertEquals(1, output.getVersion()); + } + + { + MultiVersionStructV3 v3 = new MultiVersionStructV3(); + v3.fInt32 = 123; + v3.fRect = newRect(5); + v3.fString = "hello"; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v3.serialize(null)); + assertEquals(expected, output); + assertEquals(3, v3.getVersion()); + assertEquals(3, output.getVersion()); + } + + { + MultiVersionStructV5 v5 = new MultiVersionStructV5(); + v5.fInt32 = 123; + v5.fRect = newRect(5); + v5.fString = "hello"; + v5.fArray = new byte[] {10, 9, 8}; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v5.serialize(null)); + assertEquals(expected, output); + assertEquals(5, v5.getVersion()); + assertEquals(5, output.getVersion()); + } + + { + int expectedHandle = 42; + MultiVersionStructV7 v7 = new MultiVersionStructV7(); + v7.fInt32 = 123; + v7.fRect = newRect(5); + v7.fString = "hello"; + v7.fArray = new byte[] {10, 9, 8}; + v7.fMessagePipe = CoreImpl.getInstance() + .acquireNativeHandle(expectedHandle) + .toMessagePipeHandle(); + v7.fBool = true; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + expected.fBool = true; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v7.serialize(null)); + + // Handles must be tested separately. + assertEquals(expectedHandle, output.fMessagePipe.releaseNativeHandle()); + output.fMessagePipe = expected.fMessagePipe; + + assertEquals(expected, output); + assertEquals(7, v7.getVersion()); + assertEquals(7, output.getVersion()); + } + } + + /** + * Testing serializing new struct version to older one. + */ + @SmallTest + public void testNewToOld() { + MultiVersionStruct struct = newStruct(); + { + MultiVersionStructV0 expected = new MultiVersionStructV0(); + expected.fInt32 = 123; + + MultiVersionStructV0 output = MultiVersionStructV0.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV1 expected = new MultiVersionStructV1(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + + MultiVersionStructV1 output = MultiVersionStructV1.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV3 expected = new MultiVersionStructV3(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + + MultiVersionStructV3 output = MultiVersionStructV3.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV5 expected = new MultiVersionStructV5(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + + MultiVersionStructV5 output = MultiVersionStructV5.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + int expectedHandle = 42; + MultiVersionStructV7 expected = new MultiVersionStructV7(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + expected.fBool = true; + + MultiVersionStruct input = struct; + input.fMessagePipe = CoreImpl.getInstance() + .acquireNativeHandle(expectedHandle) + .toMessagePipeHandle(); + + MultiVersionStructV7 output = MultiVersionStructV7.deserialize(input.serialize(null)); + + assertEquals(expectedHandle, output.fMessagePipe.releaseNativeHandle()); + output.fMessagePipe = expected.fMessagePipe; + + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java new file mode 100644 index 0000000000..497be65af2 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java @@ -0,0 +1,59 @@ +// 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.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.bindings.Callbacks.Callback1; +import org.chromium.mojo.bindings.Callbacks.Callback7; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Testing generated callbacks + */ +public class CallbacksTest extends TestCase { + + /** + * Testing {@link Callback1}. + */ + @SmallTest + public void testCallback1() { + final List<Integer> parameters = new ArrayList<Integer>(); + new Callback1<Integer>() { + @Override + public void call(Integer i1) { + parameters.add(i1); + } + }.call(1); + assertEquals(Arrays.asList(1), parameters); + } + + /** + * Testing {@link Callback7}. + */ + @SmallTest + public void testCallback7() { + final List<Integer> parameters = new ArrayList<Integer>(); + new Callback7<Integer, Integer, Integer, Integer, Integer, Integer, Integer>() { + @Override + public void call(Integer i1, Integer i2, Integer i3, Integer i4, Integer i5, Integer i6, + Integer i7) { + parameters.add(i1); + parameters.add(i2); + parameters.add(i3); + parameters.add(i4); + parameters.add(i5); + parameters.add(i6); + parameters.add(i7); + } + }.call(1, 2, 3, 4, 5, 6, 7); + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7), parameters); + } +} 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 0000000000..15f9f1fa33 --- /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.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +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.ResultAnd; +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 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( + new MessagePipeHandle.CreateOptions()); + mHandle = handles.first; + mConnector = new Connector(handles.second); + mReceiver = new RecordingMessageReceiver(); + mConnector.setIncomingMessageReceiver(mReceiver); + mErrorHandler = new CapturingErrorHandler(); + mConnector.setErrorHandler(mErrorHandler); + mConnector.start(); + mTestMessage = BindingsTestUtils.newRandomMessage(DATA_LENGTH); + assertNull(mErrorHandler.getLastMojoException()); + 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.getLastMojoException()); + ByteBuffer received = ByteBuffer.allocateDirect(DATA_LENGTH); + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + mHandle.readMessage(received, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(DATA_LENGTH, result.getValue().getMessageSize()); + assertEquals(mTestMessage.getData(), received); + } + + /** + * Test receiving a message through a {@link Connector} + */ + @SmallTest + public void testReceivingMessage() { + mHandle.writeMessage(mTestMessage.getData(), new ArrayList<Handle>(), + MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + assertNull(mErrorHandler.getLastMojoException()); + assertEquals(1, mReceiver.messages.size()); + Message received = mReceiver.messages.get(0); + assertEquals(0, received.getHandles().size()); + assertEquals(mTestMessage.getData(), received.getData()); + } + + /** + * Test receiving an error through a {@link Connector}. + */ + @SmallTest + public void testErrors() { + mHandle.close(); + runLoopUntilIdle(); + assertNotNull(mErrorHandler.getLastMojoException()); + assertEquals(MojoResult.FAILED_PRECONDITION, + mErrorHandler.getLastMojoException().getMojoResult()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java new file mode 100644 index 0000000000..cabe2306b8 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java @@ -0,0 +1,104 @@ +// 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.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Testing the executor factory. + */ +public class ExecutorFactoryTest extends MojoTestCase { + + private static final long RUN_LOOP_TIMEOUT_MS = 50; + private static final int CONCURRENCY_LEVEL = 5; + private static final ExecutorService WORKERS = Executors.newFixedThreadPool(CONCURRENCY_LEVEL); + + private Executor mExecutor; + private List<Thread> mThreadContainer; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + mExecutor = ExecutorFactory.getExecutorForCurrentThread(CoreImpl.getInstance()); + mThreadContainer = new ArrayList<Thread>(); + } + + /** + * Testing the {@link Executor} when called from the executor thread. + */ + @SmallTest + public void testExecutorOnCurrentThread() { + Runnable action = new Runnable() { + @Override + public void run() { + mThreadContainer.add(Thread.currentThread()); + } + }; + mExecutor.execute(action); + mExecutor.execute(action); + assertEquals(0, mThreadContainer.size()); + runLoop(RUN_LOOP_TIMEOUT_MS); + assertEquals(2, mThreadContainer.size()); + for (Thread thread : mThreadContainer) { + assertEquals(Thread.currentThread(), thread); + } + } + + /** + * Testing the {@link Executor} when called from another thread. + */ + @SmallTest + public void testExecutorOnOtherThread() { + final CyclicBarrier barrier = new CyclicBarrier(CONCURRENCY_LEVEL + 1); + for (int i = 0; i < CONCURRENCY_LEVEL; ++i) { + WORKERS.execute(new Runnable() { + @Override + public void run() { + mExecutor.execute(new Runnable() { + + @Override + public void run() { + mThreadContainer.add(Thread.currentThread()); + } + }); + try { + barrier.await(); + } catch (InterruptedException e) { + fail("Unexpected exception: " + e.getMessage()); + } catch (BrokenBarrierException e) { + fail("Unexpected exception: " + e.getMessage()); + } + } + }); + } + try { + barrier.await(); + } catch (InterruptedException e) { + fail("Unexpected exception: " + e.getMessage()); + } catch (BrokenBarrierException e) { + fail("Unexpected exception: " + e.getMessage()); + } + assertEquals(0, mThreadContainer.size()); + runLoop(RUN_LOOP_TIMEOUT_MS); + assertEquals(CONCURRENCY_LEVEL, mThreadContainer.size()); + for (Thread thread : mThreadContainer) { + assertEquals(Thread.currentThread(), thread); + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java new file mode 100644 index 0000000000..8cdd4abf29 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java @@ -0,0 +1,129 @@ +// Copyright 2015 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.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.Callbacks.Callback1; +import org.chromium.mojo.bindings.test.mojom.sample.Enum; +import org.chromium.mojo.bindings.test.mojom.sample.IntegerAccessor; +import org.chromium.mojo.system.MojoException; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Tests for interface control messages. + */ +public class InterfaceControlMessageTest extends MojoTestCase { + private final List<Closeable> mCloseablesToClose = new ArrayList<Closeable>(); + + /** + * See mojo/public/interfaces/bindings/tests/sample_interfaces.mojom. + */ + class IntegerAccessorImpl extends SideEffectFreeCloseable implements IntegerAccessor { + private long mValue = 0; + private int mEnum = 0; + private boolean mEncounteredError = false; + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + mEncounteredError = true; + } + + /** + * @see IntegerAccessor#getInteger(IntegerAccessor.GetIntegerResponse) + */ + @Override + public void getInteger(GetIntegerResponse response) { + response.call(mValue, mEnum); + } + + /** + * @see IntegerAccessor#setInteger(long, int) + */ + @Override + public void setInteger(long value, int enumValue) { + mValue = value; + mEnum = enumValue; + } + + public long getValue() { + return mValue; + } + + public boolean encounteredError() { + return mEncounteredError; + } + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + // Close the elements in the reverse order they were added. This is needed because it is an + // error to close the handle of a proxy without closing the proxy first. + Collections.reverse(mCloseablesToClose); + for (Closeable c : mCloseablesToClose) { + c.close(); + } + super.tearDown(); + } + + @SmallTest + public void testQueryVersion() { + IntegerAccessor.Proxy p = BindingsTestUtils.newProxyOverPipe( + IntegerAccessor.MANAGER, new IntegerAccessorImpl(), mCloseablesToClose); + assertEquals(0, p.getProxyHandler().getVersion()); + p.getProxyHandler().queryVersion(new Callback1<Integer>() { + @Override + public void call(Integer version) { + assertEquals(3, version.intValue()); + } + }); + runLoopUntilIdle(); + assertEquals(3, p.getProxyHandler().getVersion()); + } + + @SmallTest + public void testRequireVersion() { + IntegerAccessorImpl impl = new IntegerAccessorImpl(); + IntegerAccessor.Proxy p = BindingsTestUtils.newProxyOverPipe( + IntegerAccessor.MANAGER, impl, mCloseablesToClose); + + assertEquals(0, p.getProxyHandler().getVersion()); + + p.getProxyHandler().requireVersion(1); + assertEquals(1, p.getProxyHandler().getVersion()); + p.setInteger(123, Enum.VALUE); + runLoopUntilIdle(); + assertFalse(impl.encounteredError()); + assertEquals(123, impl.getValue()); + + p.getProxyHandler().requireVersion(3); + assertEquals(3, p.getProxyHandler().getVersion()); + p.setInteger(456, Enum.VALUE); + runLoopUntilIdle(); + assertFalse(impl.encounteredError()); + assertEquals(456, impl.getValue()); + + // Require a version that is not supported by the implementation side. + p.getProxyHandler().requireVersion(4); + // getVersion() is updated synchronously. + assertEquals(4, p.getProxyHandler().getVersion()); + p.setInteger(789, Enum.VALUE); + runLoopUntilIdle(); + assertTrue(impl.encounteredError()); + // The call to setInteger() after requireVersion() is ignored. + assertEquals(456, impl.getValue()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java new file mode 100644 index 0000000000..d8bd3e85ad --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java @@ -0,0 +1,284 @@ +// 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.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.test.mojom.imported.ImportedInterface; +import org.chromium.mojo.bindings.test.mojom.sample.Factory; +import org.chromium.mojo.bindings.test.mojom.sample.NamedObject; +import org.chromium.mojo.bindings.test.mojom.sample.NamedObject.GetNameResponse; +import org.chromium.mojo.bindings.test.mojom.sample.Request; +import org.chromium.mojo.bindings.test.mojom.sample.Response; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Tests for interfaces / proxies / stubs generated for sample_factory.mojom. + */ +public class InterfacesTest extends MojoTestCase { + + private static final String OBJECT_NAME = "hello world"; + + private final List<Closeable> mCloseablesToClose = new ArrayList<Closeable>(); + + /** + * Basic implementation of {@link NamedObject}. + */ + public static class MockNamedObjectImpl extends CapturingErrorHandler implements NamedObject { + + private String mName = ""; + + /** + * @see org.chromium.mojo.bindings.Interface#close() + */ + @Override + public void close() { + } + + @Override + public void setName(String name) { + mName = name; + } + + @Override + public void getName(GetNameResponse callback) { + callback.call(mName); + } + + public String getNameSynchronously() { + return mName; + } + } + + /** + * Implementation of {@link GetNameResponse} keeping track of usage. + */ + public static class RecordingGetNameResponse implements GetNameResponse { + private String mName; + private boolean mCalled; + + public RecordingGetNameResponse() { + reset(); + } + + @Override + public void call(String name) { + mName = name; + mCalled = true; + } + + public String getName() { + return mName; + } + + public boolean wasCalled() { + return mCalled; + } + + public void reset() { + mName = null; + mCalled = false; + } + } + + /** + * Basic implementation of {@link Factory}. + */ + public class MockFactoryImpl extends CapturingErrorHandler implements Factory { + + private boolean mClosed = false; + + public boolean isClosed() { + return mClosed; + } + + /** + * @see org.chromium.mojo.bindings.Interface#close() + */ + @Override + public void close() { + mClosed = true; + } + + @Override + public void doStuff(Request request, MessagePipeHandle pipe, DoStuffResponse callback) { + if (pipe != null) { + pipe.close(); + } + Response response = new Response(); + response.x = 42; + callback.call(response, "Hello"); + } + + @Override + public void doStuff2(ConsumerHandle pipe, DoStuff2Response callback) { + callback.call("World"); + } + + @Override + public void createNamedObject(InterfaceRequest<NamedObject> obj) { + NamedObject.MANAGER.bind(new MockNamedObjectImpl(), obj); + } + + @Override + public void requestImportedInterface(InterfaceRequest<ImportedInterface> obj, + RequestImportedInterfaceResponse callback) { + throw new UnsupportedOperationException("Not implemented."); + } + + @Override + public void takeImportedInterface(ImportedInterface obj, + TakeImportedInterfaceResponse callback) { + throw new UnsupportedOperationException("Not implemented."); + } + } + + /** + * Implementation of DoStuffResponse that keeps track of if the response is called. + */ + public static class DoStuffResponseImpl implements Factory.DoStuffResponse { + private boolean mResponseCalled = false; + + public boolean wasResponseCalled() { + return mResponseCalled; + } + + @Override + public void call(Response response, String string) { + mResponseCalled = true; + } + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + // Close the elements in the reverse order they were added. This is needed because it is an + // error to close the handle of a proxy without closing the proxy first. + Collections.reverse(mCloseablesToClose); + for (Closeable c : mCloseablesToClose) { + c.close(); + } + super.tearDown(); + } + + /** + * Check that the given proxy receives the calls. If |impl| is not null, also check that the + * calls are forwared to |impl|. + */ + private void checkProxy(NamedObject.Proxy proxy, MockNamedObjectImpl impl) { + RecordingGetNameResponse callback = new RecordingGetNameResponse(); + CapturingErrorHandler errorHandler = new CapturingErrorHandler(); + proxy.getProxyHandler().setErrorHandler(errorHandler); + + if (impl != null) { + assertNull(impl.getLastMojoException()); + assertEquals("", impl.getNameSynchronously()); + } + + proxy.getName(callback); + runLoopUntilIdle(); + + assertNull(errorHandler.getLastMojoException()); + assertTrue(callback.wasCalled()); + assertEquals("", callback.getName()); + + callback.reset(); + proxy.setName(OBJECT_NAME); + runLoopUntilIdle(); + + assertNull(errorHandler.getLastMojoException()); + if (impl != null) { + assertNull(impl.getLastMojoException()); + assertEquals(OBJECT_NAME, impl.getNameSynchronously()); + } + + proxy.getName(callback); + runLoopUntilIdle(); + + assertNull(errorHandler.getLastMojoException()); + assertTrue(callback.wasCalled()); + assertEquals(OBJECT_NAME, callback.getName()); + } + + @SmallTest + public void testName() { + assertEquals("sample::NamedObject", NamedObject.MANAGER.getName()); + } + + @SmallTest + public void testProxyAndStub() { + MockNamedObjectImpl impl = new MockNamedObjectImpl(); + NamedObject.Proxy proxy = + NamedObject.MANAGER.buildProxy(null, NamedObject.MANAGER.buildStub(null, impl)); + + checkProxy(proxy, impl); + } + + @SmallTest + public void testProxyAndStubOverPipe() { + MockNamedObjectImpl impl = new MockNamedObjectImpl(); + NamedObject.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(NamedObject.MANAGER, impl, mCloseablesToClose); + + checkProxy(proxy, impl); + } + + @SmallTest + public void testFactoryOverPipe() { + Factory.Proxy proxy = BindingsTestUtils.newProxyOverPipe( + Factory.MANAGER, new MockFactoryImpl(), mCloseablesToClose); + Pair<NamedObject.Proxy, InterfaceRequest<NamedObject>> request = + NamedObject.MANAGER.getInterfaceRequest(CoreImpl.getInstance()); + mCloseablesToClose.add(request.first); + proxy.createNamedObject(request.second); + + checkProxy(request.first, null); + } + + @SmallTest + public void testInterfaceClosing() { + MockFactoryImpl impl = new MockFactoryImpl(); + Factory.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(Factory.MANAGER, impl, mCloseablesToClose); + + assertFalse(impl.isClosed()); + + proxy.close(); + runLoopUntilIdle(); + + assertTrue(impl.isClosed()); + } + + @SmallTest + public void testResponse() { + MockFactoryImpl impl = new MockFactoryImpl(); + Factory.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(Factory.MANAGER, impl, mCloseablesToClose); + Request request = new Request(); + request.x = 42; + Pair<MessagePipeHandle, MessagePipeHandle> handles = + CoreImpl.getInstance().createMessagePipe(null); + DoStuffResponseImpl response = new DoStuffResponseImpl(); + proxy.doStuff(request, handles.first, response); + + assertFalse(response.wasResponseCalled()); + + runLoopUntilIdle(); + + assertTrue(response.wasResponseCalled()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java new file mode 100644 index 0000000000..b2e6ac8521 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java @@ -0,0 +1,69 @@ +// 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.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.bindings.test.mojom.imported.Point; + +/** + * Testing internal classes of interfaces. + */ +public class MessageHeaderTest extends TestCase { + + /** + * Testing that headers are identical after being serialized/deserialized. + */ + @SmallTest + public void testSimpleMessageHeader() { + final int xValue = 1; + final int yValue = 2; + final int type = 6; + Point p = new Point(); + p.x = xValue; + p.y = yValue; + ServiceMessage message = p.serializeWithHeader(null, new MessageHeader(type)); + + MessageHeader header = message.getHeader(); + assertTrue(header.validateHeader(type, 0)); + assertEquals(type, header.getType()); + assertEquals(0, header.getFlags()); + + Point p2 = Point.deserialize(message.getPayload()); + assertNotNull(p2); + assertEquals(p.x, p2.x); + assertEquals(p.y, p2.y); + } + + /** + * Testing that headers are identical after being serialized/deserialized. + */ + @SmallTest + public void testMessageWithRequestIdHeader() { + final int xValue = 1; + final int yValue = 2; + final int type = 6; + final long requestId = 0x1deadbeafL; + Point p = new Point(); + p.x = xValue; + p.y = yValue; + ServiceMessage message = p.serializeWithHeader(null, + new MessageHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0)); + message.setRequestId(requestId); + + MessageHeader header = message.getHeader(); + assertTrue(header.validateHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)); + assertEquals(type, header.getType()); + assertEquals(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, header.getFlags()); + assertEquals(requestId, header.getRequestId()); + + Point p2 = Point.deserialize(message.getPayload()); + assertNotNull(p2); + assertEquals(p.x, p2.x); + assertEquals(p.y, p2.y); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java new file mode 100644 index 0000000000..51dbd22800 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java @@ -0,0 +1,109 @@ +// 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.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +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; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +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; +import java.util.Arrays; +import java.util.List; + +/** + * Testing {@link Connector#readAndDispatchMessage}. + */ +public class ReadAndDispatchMessageTest extends MojoTestCase { + + private static final int DATA_SIZE = 1024; + + private ByteBuffer mData; + private Pair<MessagePipeHandle, MessagePipeHandle> mHandles; + private List<Handle> mHandlesToSend = new ArrayList<Handle>(); + private List<Handle> mHandlesToClose = new ArrayList<Handle>(); + private RecordingMessageReceiver mMessageReceiver; + + /** + * @see org.chromium.mojo.MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + Core core = CoreImpl.getInstance(); + mData = BindingsTestUtils.newRandomMessage(DATA_SIZE).getData(); + mMessageReceiver = new RecordingMessageReceiver(); + mHandles = core.createMessagePipe(new MessagePipeHandle.CreateOptions()); + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> datapipe = core.createDataPipe(null); + mHandlesToSend.addAll(Arrays.asList(datapipe.first, datapipe.second)); + mHandlesToClose.addAll(Arrays.asList(mHandles.first, mHandles.second)); + mHandlesToClose.addAll(mHandlesToSend); + } + + /** + * @see org.chromium.mojo.MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + for (Handle handle : mHandlesToClose) { + handle.close(); + } + super.tearDown(); + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + */ + @SmallTest + public void testReadAndDispatchMessage() { + mHandles.first.writeMessage(mData, mHandlesToSend, MessagePipeHandle.WriteFlags.NONE); + assertEquals(MojoResult.OK, Connector.readAndDispatchMessage(mHandles.second, + mMessageReceiver).getMojoResult()); + assertEquals(1, mMessageReceiver.messages.size()); + Message message = mMessageReceiver.messages.get(0); + mHandlesToClose.addAll(message.getHandles()); + assertEquals(mData, message.getData()); + assertEquals(2, message.getHandles().size()); + for (Handle handle : message.getHandles()) { + assertTrue(handle.isValid()); + } + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + * with no message available. + */ + @SmallTest + public void testReadAndDispatchMessageOnEmptyHandle() { + assertEquals(MojoResult.SHOULD_WAIT, Connector.readAndDispatchMessage(mHandles.second, + mMessageReceiver).getMojoResult()); + assertEquals(0, mMessageReceiver.messages.size()); + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + * on closed handle. + */ + @SmallTest + public void testReadAndDispatchMessageOnClosedHandle() { + mHandles.first.close(); + try { + Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver); + fail("MojoException should have been thrown"); + } catch (MojoException expected) { + assertEquals(MojoResult.FAILED_PRECONDITION, expected.getMojoResult()); + } + assertEquals(0, mMessageReceiver.messages.size()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java new file mode 100644 index 0000000000..6aa1726b4c --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java @@ -0,0 +1,231 @@ +// 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.support.test.filters.SmallTest; + +import org.chromium.base.annotations.SuppressFBWarnings; +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiverWithResponder; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignals; +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.ResultAnd; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Testing {@link Router} + */ +public class RouterTest extends MojoTestCase { + + private MessagePipeHandle mHandle; + private Router mRouter; + private RecordingMessageReceiverWithResponder 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(null); + mHandle = handles.first; + mRouter = new RouterImpl(handles.second); + mReceiver = new RecordingMessageReceiverWithResponder(); + mRouter.setIncomingMessageReceiver(mReceiver); + mErrorHandler = new CapturingErrorHandler(); + mRouter.setErrorHandler(mErrorHandler); + mRouter.start(); + } + + /** + * Testing sending a message via the router that expected a response. + */ + @SmallTest + public void testSendingToRouterWithResponse() { + final int requestMessageType = 0xdead; + final int responseMessageType = 0xbeaf; + + // Sending a message expecting a response. + MessageHeader header = new MessageHeader(requestMessageType, + MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0); + Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + header.encode(encoder); + mRouter.acceptWithResponder(encoder.getMessage(), mReceiver); + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(header.getSize()); + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + mHandle.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + + assertEquals(MojoResult.OK, result.getMojoResult()); + MessageHeader receivedHeader = new Message( + receiveBuffer, new ArrayList<Handle>()).asServiceMessage().getHeader(); + + assertEquals(header.getType(), receivedHeader.getType()); + assertEquals(header.getFlags(), receivedHeader.getFlags()); + assertTrue(receivedHeader.getRequestId() != 0); + + // Sending the response. + MessageHeader responseHeader = new MessageHeader(responseMessageType, + MessageHeader.MESSAGE_IS_RESPONSE_FLAG, receivedHeader.getRequestId()); + encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + responseHeader.encode(encoder); + Message responseMessage = encoder.getMessage(); + mHandle.writeMessage(responseMessage.getData(), new ArrayList<Handle>(), + MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + + assertEquals(1, mReceiver.messages.size()); + ServiceMessage receivedResponseMessage = mReceiver.messages.get(0).asServiceMessage(); + assertEquals(MessageHeader.MESSAGE_IS_RESPONSE_FLAG, + receivedResponseMessage.getHeader().getFlags()); + assertEquals(responseMessage.getData(), receivedResponseMessage.getData()); + } + + /** + * Sends a message to the Router. + * + * @param messageIndex Used when sending multiple messages to indicate the index of this + * message. + * @param requestMessageType The message type to use in the header of the sent message. + * @param requestId The requestId to use in the header of the sent message. + */ + private void sendMessageToRouter(int messageIndex, int requestMessageType, int requestId) { + MessageHeader header = new MessageHeader( + requestMessageType, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, requestId); + Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + header.encode(encoder); + Message headerMessage = encoder.getMessage(); + mHandle.writeMessage(headerMessage.getData(), new ArrayList<Handle>(), + MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + + assertEquals(messageIndex + 1, mReceiver.messagesWithReceivers.size()); + Pair<Message, MessageReceiver> receivedMessage = + mReceiver.messagesWithReceivers.get(messageIndex); + assertEquals(headerMessage.getData(), receivedMessage.first.getData()); + } + + /** + * Sends a response message from the Router. + * + * @param messageIndex Used when sending responses to multiple messages to indicate the index + * of the message that this message is a response to. + * @param responseMessageType The message type to use in the header of the response message. + */ + private void sendResponseFromRouter(int messageIndex, int responseMessageType) { + Pair<Message, MessageReceiver> receivedMessage = + mReceiver.messagesWithReceivers.get(messageIndex); + + long requestId = receivedMessage.first.asServiceMessage().getHeader().getRequestId(); + + MessageHeader responseHeader = new MessageHeader( + responseMessageType, MessageHeader.MESSAGE_IS_RESPONSE_FLAG, requestId); + Encoder encoder = new Encoder(CoreImpl.getInstance(), responseHeader.getSize()); + responseHeader.encode(encoder); + Message message = encoder.getMessage(); + receivedMessage.second.accept(message); + + ByteBuffer receivedResponseMessage = ByteBuffer.allocateDirect(responseHeader.getSize()); + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + mHandle.readMessage(receivedResponseMessage, 0, MessagePipeHandle.ReadFlags.NONE); + + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(message.getData(), receivedResponseMessage); + } + + /** + * Clears {@code mReceiver.messagesWithReceivers} allowing all message receivers to be + * finalized. + * <p> + * Since there is no way to force the Garbage Collector to actually call finalize and we want to + * test the effects of the finalize() method, we explicitly call finalize() on all of the + * message receivers. We do this in a custom thread to better approximate what the JVM does. + */ + private void clearAllMessageReceivers() { + Thread myFinalizerThread = new Thread() { + @Override + @SuppressFBWarnings("FI_EXPLICIT_INVOCATION") + public void run() { + for (Pair<Message, MessageReceiver> receivedMessage : + mReceiver.messagesWithReceivers) { + RouterImpl.ResponderThunk thunk = + (RouterImpl.ResponderThunk) receivedMessage.second; + try { + thunk.finalize(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + } + }; + myFinalizerThread.start(); + try { + myFinalizerThread.join(); + } catch (InterruptedException e) { + // ignore. + } + mReceiver.messagesWithReceivers.clear(); + } + + /** + * Testing receiving a message via the router that expected a response. + */ + @SmallTest + public void testReceivingViaRouterWithResponse() { + final int requestMessageType = 0xdead; + final int responseMessageType = 0xbeef; + final int requestId = 0xdeadbeaf; + + // Send a message expecting a response. + sendMessageToRouter(0, requestMessageType, requestId); + + // Sending the response. + sendResponseFromRouter(0, responseMessageType); + } + + /** + * Tests that if a callback is dropped (i.e. becomes unreachable and is finalized + * without being used), then the message pipe will be closed. + */ + @SmallTest + public void testDroppingReceiverWithoutUsingIt() { + // Send 10 messages to the router without sending a response. + for (int i = 0; i < 10; i++) { + sendMessageToRouter(i, i, i); + } + + // Now send the 10 responses. This should work fine. + for (int i = 0; i < 10; i++) { + sendResponseFromRouter(i, i); + } + + // Clear all MessageRecievers so that the ResponderThunks will + // be finalized. + clearAllMessageReceivers(); + + // Send another message to the router without sending a response. + sendMessageToRouter(0, 0, 0); + + // Clear the MessageReciever so that the ResponderThunk will + // be finalized. Since the RespondeThunk was never used, this + // should close the pipe. + clearAllMessageReceivers(); + // The close() occurs asynchronously on this thread. + runLoopUntilIdle(); + + // Confirm that the pipe was closed on the Router side. + HandleSignals closedFlag = HandleSignals.none().setPeerClosed(true); + assertEquals(closedFlag, mHandle.querySignalsState().getSatisfiedSignals()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java new file mode 100644 index 0000000000..2c17e3a194 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java @@ -0,0 +1,175 @@ +// 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.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct1; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct2; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct3; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct4; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct5; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct6; +import org.chromium.mojo.bindings.test.mojom.mojo.StructOfNullables; + +import java.nio.ByteBuffer; + +/** + * Tests for the serialization logic of the generated structs, using structs defined in + * mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom . + */ +public class SerializationTest extends TestCase { + + private static void assertThrowsSerializationException(Struct struct) { + try { + struct.serialize(null); + fail("Serialization of invalid struct should have thrown an exception."); + } catch (SerializationException ex) { + // Expected. + } + } + + /** + * Verifies that serializing a struct with an invalid handle of a non-nullable type throws an + * exception. + */ + @SmallTest + public void testHandle() { + Struct2 struct = new Struct2(); + assertFalse(struct.hdl.isValid()); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.hdl = new HandleMock(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a null struct pointer throws an exception. + */ + @SmallTest + public void testStructPointer() { + Struct3 struct = new Struct3(); + assertNull(struct.struct1); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.struct1 = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with an array of structs throws an exception when the + * struct is invalid. + */ + @SmallTest + public void testStructArray() { + Struct4 struct = new Struct4(); + assertNull(struct.data); + assertThrowsSerializationException(struct); + + // Create the (1-element) array but have the element null. + struct.data = new Struct1[1]; + assertThrowsSerializationException(struct); + + // Create the array element, struct should serialize now. + struct.data[0] = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a fixed-size array of incorrect length throws an + * exception. + */ + @SmallTest + public void testFixedSizeArray() { + Struct5 struct = new Struct5(); + assertNull(struct.pair); + assertThrowsSerializationException(struct); + + // Create the (1-element) array, 2-element array is required. + struct.pair = new Struct1[1]; + struct.pair[0] = new Struct1(); + assertThrowsSerializationException(struct); + + // Create the array of a correct size, struct should serialize now. + struct.pair = new Struct1[2]; + struct.pair[0] = new Struct1(); + struct.pair[1] = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a null string throws an exception. + */ + @SmallTest + public void testString() { + Struct6 struct = new Struct6(); + assertNull(struct.str); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.str = ""; + struct.serialize(null); + } + + /** + * Verifies that a struct with an invalid nullable handle, null nullable struct pointer and null + * nullable string serializes without an exception. + */ + @SmallTest + public void testNullableFields() { + StructOfNullables struct = new StructOfNullables(); + assertFalse(struct.hdl.isValid()); + assertNull(struct.struct1); + assertNull(struct.str); + struct.serialize(null); + } + + /** + * Verifies that a struct can be serialized to and deserialized from a ByteBuffer. + */ + @SmallTest + public void testByteBufferSerialization() { + Struct1 input = new Struct1(); + input.i = 0x7F; + + ByteBuffer buf = input.serialize(); + + byte[] expected_raw_bytes = {16, 0, 0, 0, 0, 0, 0, 0, 0x7F, 0, 0, 0, 0, 0, 0, 0}; + ByteBuffer expected_buf = ByteBuffer.wrap(expected_raw_bytes); + assertEquals(expected_buf, buf); + + Struct1 output = Struct1.deserialize(buf); + assertEquals(0x7F, output.i); + } + + /** + * Verifies that a struct with handles cannot be serialized to a ByteBuffer. + */ + @SmallTest + public void testByteBufferSerializationWithHandles() { + StructOfNullables struct = new StructOfNullables(); + assertFalse(struct.hdl.isValid()); + assertNull(struct.struct1); + assertNull(struct.str); + + // It is okay to serialize invalid handles. + struct.serialize(); + + struct.hdl = new HandleMock(); + + try { + struct.serialize(); + fail("Serializing a struct with handles to a ByteBuffer should have thrown an " + + "exception."); + } catch (UnsupportedOperationException ex) { + // Expected. + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java new file mode 100644 index 0000000000..84246188d3 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java @@ -0,0 +1,237 @@ +// 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.support.test.filters.SmallTest; + +import org.chromium.base.test.util.UrlUtils; +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.test.mojom.mojo.ConformanceTestInterface; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterfaceTestHelper; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +/** + * Testing validation upon deserialization using the interfaces defined in the + * mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom file. + * <p> + * One needs to pass '--test_data=bindings:{path to mojo/public/interfaces/bindings/tests/data}' to + * the test_runner script for this test to find the validation data it needs. + */ +public class ValidationTest extends MojoTestCase { + + /** + * The path where validation test data is. + */ + private static final File VALIDATION_TEST_DATA_PATH = + new File(UrlUtils.getIsolatedTestFilePath( + "mojo/public/interfaces/bindings/tests/data/validation")); + + /** + * The data needed for a validation test. + */ + private static class TestData { + public File dataFile; + public ValidationTestUtil.Data inputData; + public String expectedResult; + } + + private static class DataFileFilter implements FileFilter { + private final String mPrefix; + + public DataFileFilter(String prefix) { + this.mPrefix = prefix; + } + + @Override + public boolean accept(File pathname) { + // TODO(yzshen, qsr): skip some interface versioning tests. + if (pathname.getName().startsWith("conformance_mthd13_good_2")) { + return false; + } + return pathname.isFile() && pathname.getName().startsWith(mPrefix) + && pathname.getName().endsWith(".data"); + } + } + + private static String getStringContent(File f) throws FileNotFoundException { + try (Scanner scanner = new Scanner(f)) { + scanner.useDelimiter("\\Z"); + StringBuilder result = new StringBuilder(); + while (scanner.hasNext()) { + result.append(scanner.next()); + } + return result.toString().trim(); + } + } + + private static List<TestData> getTestData(String prefix) + throws FileNotFoundException { + List<TestData> results = new ArrayList<TestData>(); + + // Fail if the test data is not present. + if (!VALIDATION_TEST_DATA_PATH.isDirectory()) { + fail("No test data directory found. " + + "Expected directory at: " + VALIDATION_TEST_DATA_PATH); + } + + File[] files = VALIDATION_TEST_DATA_PATH.listFiles(new DataFileFilter(prefix)); + if (files != null) { + for (File dataFile : files) { + File resultFile = new File(dataFile.getParent(), + dataFile.getName().replaceFirst("\\.data$", ".expected")); + TestData testData = new TestData(); + testData.dataFile = dataFile; + testData.inputData = ValidationTestUtil.parseData(getStringContent(dataFile)); + testData.expectedResult = getStringContent(resultFile); + results.add(testData); + } + } + return results; + } + + /** + * Runs all the test with the given prefix on the given {@link MessageReceiver}. + */ + private static void runTest(String prefix, MessageReceiver messageReceiver) + throws FileNotFoundException { + List<TestData> testData = getTestData(prefix); + for (TestData test : testData) { + assertNull("Unable to read: " + test.dataFile.getName() + + ": " + test.inputData.getErrorMessage(), + test.inputData.getErrorMessage()); + List<Handle> handles = new ArrayList<Handle>(); + for (int i = 0; i < test.inputData.getHandlesCount(); ++i) { + handles.add(new HandleMock()); + } + Message message = new Message(test.inputData.getData(), handles); + boolean passed = messageReceiver.accept(message); + if (passed && !test.expectedResult.equals("PASS")) { + fail("Input: " + test.dataFile.getName() + + ": The message should have been refused. Expected error: " + + test.expectedResult); + } + if (!passed && test.expectedResult.equals("PASS")) { + fail("Input: " + test.dataFile.getName() + + ": The message should have been accepted."); + } + } + } + + private static class RoutingMessageReceiver implements MessageReceiver { + private final MessageReceiverWithResponder mRequest; + private final MessageReceiver mResponse; + + private RoutingMessageReceiver(MessageReceiverWithResponder request, + MessageReceiver response) { + this.mRequest = request; + this.mResponse = response; + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + try { + MessageHeader header = message.asServiceMessage().getHeader(); + if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) { + return mResponse.accept(message); + } else { + return mRequest.acceptWithResponder(message, new SinkMessageReceiver()); + } + } catch (DeserializationException e) { + return false; + } + } + + /** + * @see MessageReceiver#close() + */ + @Override + public void close() { + } + + } + + /** + * A trivial message receiver that refuses all messages it receives. + */ + private static class SinkMessageReceiver implements MessageReceiverWithResponder { + + @Override + public boolean accept(Message message) { + return true; + } + + @Override + public void close() { + } + + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + return true; + } + } + + /** + * Testing the conformance suite. + */ + @SmallTest + public void testConformance() throws FileNotFoundException { + runTest("conformance_", + ConformanceTestInterface.MANAGER.buildStub(CoreImpl.getInstance(), + ConformanceTestInterface.MANAGER.buildProxy( + CoreImpl.getInstance(), new SinkMessageReceiver()))); + } + + /** + * Testing the integration suite for message headers. + */ + @SmallTest + public void testIntegrationMessageHeader() throws FileNotFoundException { + runTest("integration_msghdr_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy(null, + new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } + + /** + * Testing the integration suite for request messages. + */ + @SmallTest + public void testIntegrationRequestMessage() throws FileNotFoundException { + runTest("integration_intf_rqst_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy(null, + new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } + + /** + * Testing the integration suite for response messages. + */ + @SmallTest + public void testIntegrationResponseMessage() throws FileNotFoundException { + runTest("integration_intf_resp_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy(null, + new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java new file mode 100644 index 0000000000..91b993c3b6 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java @@ -0,0 +1,68 @@ +// 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.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Utility class for testing message validation. The file format used to describe a message is + * described in The format is described in + * mojo/public/cpp/bindings/tests/validation_test_input_parser.h + */ +@JNINamespace("mojo::android") +public class ValidationTestUtil { + + /** + * Content of a '.data' file. + */ + public static class Data { + private final ByteBuffer mData; + private final int mHandlesCount; + private final String mErrorMessage; + + public ByteBuffer getData() { + return mData; + } + + public int getHandlesCount() { + return mHandlesCount; + } + + public String getErrorMessage() { + return mErrorMessage; + } + + private Data(ByteBuffer data, int handlesCount, String errorMessage) { + this.mData = data; + this.mHandlesCount = handlesCount; + this.mErrorMessage = errorMessage; + } + } + + /** + * Parse a '.data' file. + */ + public static Data parseData(String dataAsString) { + return nativeParseData(dataAsString); + } + + private static native Data nativeParseData(String dataAsString); + + @CalledByNative + private static Data buildData(ByteBuffer data, int handlesCount, String errorMessage) { + ByteBuffer copiedData = null; + if (data != null) { + copiedData = ByteBuffer.allocateDirect(data.limit()); + copiedData.order(ByteOrder.LITTLE_ENDIAN); + copiedData.put(data); + copiedData.flip(); + } + return new Data(copiedData, handlesCount, errorMessage); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java new file mode 100644 index 0000000000..623abc3ed2 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java @@ -0,0 +1,151 @@ +// 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.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Testing {@link ValidationTestUtil}. + */ +public class ValidationTestUtilTest extends MojoTestCase { + + /** + * Check that the input parser is correct on a given input. + */ + public static void checkInputParser( + String input, boolean isInputValid, ByteBuffer expectedData, int expectedHandlesCount) { + ValidationTestUtil.Data data = ValidationTestUtil.parseData(input); + if (isInputValid) { + assertNull(data.getErrorMessage()); + assertEquals(expectedData, data.getData()); + assertEquals(expectedHandlesCount, data.getHandlesCount()); + } else { + assertNotNull(data.getErrorMessage()); + assertNull(data.getData()); + } + } + + /** + * Testing {@link ValidationTestUtil#parseData(String)}. + */ + @SmallTest + public void testCorrectMessageParsing() { + { + // Test empty input. + String input = ""; + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.LITTLE_ENDIAN); + + checkInputParser(input, true, expected, 0); + } + { + // Test input that only consists of comments and whitespaces. + String input = " \t // hello world \n\r \t// the answer is 42 "; + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.nativeOrder()); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[u1]0x10// hello world !! \n\r \t [u2]65535 \n" + + "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff"; + ByteBuffer expected = ByteBuffer.allocateDirect(17); + expected.order(ByteOrder.nativeOrder()); + expected.put((byte) 0x10); + expected.putShort((short) 65535); + expected.putInt(65536); + expected.putLong(-1); + expected.put((byte) 0); + expected.put((byte) 0xff); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40"; + ByteBuffer expected = ByteBuffer.allocateDirect(15); + expected.order(ByteOrder.nativeOrder()); + expected.putLong(-0x800); + expected.put((byte) -128); + expected.putShort((short) 0); + expected.putInt(-40); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[b]00001011 [b]10000000 // hello world\r [b]00000000"; + ByteBuffer expected = ByteBuffer.allocateDirect(3); + expected.order(ByteOrder.nativeOrder()); + expected.put((byte) 11); + expected.put((byte) 128); + expected.put((byte) 0); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[f]+.3e9 [d]-10.03"; + ByteBuffer expected = ByteBuffer.allocateDirect(12); + expected.order(ByteOrder.nativeOrder()); + expected.putFloat(+.3e9f); + expected.putDouble(-10.03); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar"; + ByteBuffer expected = ByteBuffer.allocateDirect(14); + expected.order(ByteOrder.nativeOrder()); + expected.putInt(14); + expected.put((byte) 0); + expected.putLong(9); + expected.put((byte) 0); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "// This message has handles! \n[handles]50 [u8]2"; + ByteBuffer expected = ByteBuffer.allocateDirect(8); + expected.order(ByteOrder.nativeOrder()); + expected.putLong(2); + expected.flip(); + + checkInputParser(input, true, expected, 50); + } + + // Test some failure cases. + { + String error_inputs[] = { + "/ hello world", + "[u1]x", + "[u2]-1000", + "[u1]0x100", + "[s2]-0x8001", + "[b]1", + "[b]1111111k", + "[dist4]unmatched", + "[anchr]hello [dist8]hello", + "[dist4]a [dist4]a [anchr]a", + "[dist4]a [anchr]a [dist4]a [anchr]a", + "0 [handles]50" + }; + + for (String input : error_inputs) { + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.nativeOrder()); + checkInputParser(input, false, expected, 0); + } + } + + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java new file mode 100644 index 0000000000..8fb79d7edf --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java @@ -0,0 +1,31 @@ +// 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.test.mojom.mojo; + +import org.chromium.mojo.bindings.MessageReceiver; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface.Method0Response; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface_Internal.IntegrationTestInterfaceMethod0ResponseParamsForwardToCallback; + +/** + * Helper class to access {@link IntegrationTestInterface_Internal} package protected method for + * tests. + */ +public class IntegrationTestInterfaceTestHelper { + + private static final class SinkMethod0Response implements Method0Response { + @Override + public void call(byte[] arg1) { + } + } + + /** + * Creates a new {@link MessageReceiver} to use for the callback of + * |IntegrationTestInterface#method0(Method0Response)|. + */ + public static MessageReceiver newIntegrationTestInterfaceMethodCallback() { + return new IntegrationTestInterfaceMethod0ResponseParamsForwardToCallback( + new SinkMethod0Response()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java new file mode 100644 index 0000000000..5120198feb --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java @@ -0,0 +1,545 @@ +// 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.system.impl; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignals; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.InvalidHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.SharedBufferHandle; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +/** + * Testing the core API. + */ +public class CoreImplTest extends MojoTestCase { + private static final long RUN_LOOP_TIMEOUT_MS = 5; + + private static final ScheduledExecutorService WORKER = + Executors.newSingleThreadScheduledExecutor(); + + private static final HandleSignals ALL_SIGNALS = + HandleSignals.none().setPeerClosed(true).setReadable(true).setWritable(true); + + private List<Handle> mHandlesToClose = new ArrayList<Handle>(); + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + MojoException toThrow = null; + for (Handle handle : mHandlesToClose) { + try { + handle.close(); + } catch (MojoException e) { + if (toThrow == null) { + toThrow = e; + } + } + } + if (toThrow != null) { + throw toThrow; + } + super.tearDown(); + } + + private void addHandleToClose(Handle handle) { + mHandlesToClose.add(handle); + } + + private void addHandlePairToClose(Pair<? extends Handle, ? extends Handle> handles) { + mHandlesToClose.add(handles.first); + mHandlesToClose.add(handles.second); + } + + private static void checkSendingMessage(MessagePipeHandle in, MessagePipeHandle out) { + Random random = new Random(); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + in.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE); + + // Try to read into a small buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length / 2); + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + out.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.RESOURCE_EXHAUSTED, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().getMessageSize()); + assertEquals(0, result.getValue().getHandlesCount()); + + // Read into a correct buffer. + receiveBuffer = ByteBuffer.allocateDirect(bytes.length); + result = out.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().getMessageSize()); + assertEquals(0, result.getValue().getHandlesCount()); + assertEquals(0, receiveBuffer.position()); + assertEquals(result.getValue().getMessageSize(), receiveBuffer.limit()); + byte[] receivedBytes = new byte[result.getValue().getMessageSize()]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals(bytes, receivedBytes)); + } + + private static void checkSendingData(DataPipe.ProducerHandle in, DataPipe.ConsumerHandle out) { + Random random = new Random(); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + ResultAnd<Integer> result = in.writeData(buffer, DataPipe.WriteFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().intValue()); + + // Query number of bytes available. + ResultAnd<Integer> readResult = out.readData(null, DataPipe.ReadFlags.none().query(true)); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length, readResult.getValue().intValue()); + + // Peek data into a buffer. + ByteBuffer peekBuffer = ByteBuffer.allocateDirect(bytes.length); + readResult = out.readData(peekBuffer, DataPipe.ReadFlags.none().peek(true)); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length, readResult.getValue().intValue()); + assertEquals(bytes.length, peekBuffer.limit()); + byte[] peekBytes = new byte[bytes.length]; + peekBuffer.get(peekBytes); + assertTrue(Arrays.equals(bytes, peekBytes)); + + // Read into a buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length); + readResult = out.readData(receiveBuffer, DataPipe.ReadFlags.NONE); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length, readResult.getValue().intValue()); + assertEquals(0, receiveBuffer.position()); + assertEquals(bytes.length, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals(bytes, receivedBytes)); + } + + private static void checkSharing(SharedBufferHandle in, SharedBufferHandle out) { + Random random = new Random(); + + ByteBuffer buffer1 = in.map(0, 8, SharedBufferHandle.MapFlags.NONE); + assertEquals(8, buffer1.capacity()); + ByteBuffer buffer2 = out.map(0, 8, SharedBufferHandle.MapFlags.NONE); + assertEquals(8, buffer2.capacity()); + + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + buffer1.put(bytes); + + byte[] receivedBytes = new byte[bytes.length]; + buffer2.get(receivedBytes); + + assertTrue(Arrays.equals(bytes, receivedBytes)); + + in.unmap(buffer1); + out.unmap(buffer2); + } + + /** + * Testing that Core can be retrieved from a handle. + */ + @SmallTest + public void testGetCore() { + Core core = CoreImpl.getInstance(); + + Pair<? extends Handle, ? extends Handle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + assertEquals(core, handles.first.getCore()); + assertEquals(core, handles.second.getCore()); + + handles = core.createDataPipe(null); + addHandlePairToClose(handles); + assertEquals(core, handles.first.getCore()); + assertEquals(core, handles.second.getCore()); + + SharedBufferHandle handle = core.createSharedBuffer(null, 100); + SharedBufferHandle handle2 = handle.duplicate(null); + addHandleToClose(handle); + addHandleToClose(handle2); + assertEquals(core, handle.getCore()); + assertEquals(core, handle2.getCore()); + } + + private static void createAndCloseMessagePipe(MessagePipeHandle.CreateOptions options) { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(options); + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link MessagePipeHandle} creation. + */ + @SmallTest + public void testMessagePipeCreation() { + // Test creation with null options. + createAndCloseMessagePipe(null); + // Test creation with default options. + createAndCloseMessagePipe(new MessagePipeHandle.CreateOptions()); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeEmpty() { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + // Testing read on an empty pipe. + ResultAnd<MessagePipeHandle.ReadMessageResult> readResult = + handles.first.readMessage(null, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.SHOULD_WAIT, readResult.getMojoResult()); + + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeSend() { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + checkSendingMessage(handles.first, handles.second); + checkSendingMessage(handles.second, handles.first); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeReceiveOnSmallBuffer() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + handles.first.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE); + + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(1); + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + handles.second.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.RESOURCE_EXHAUSTED, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().getMessageSize()); + assertEquals(0, result.getValue().getHandlesCount()); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeSendHandles() { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + Pair<MessagePipeHandle, MessagePipeHandle> handlesToShare = core.createMessagePipe(null); + addHandlePairToClose(handles); + addHandlePairToClose(handlesToShare); + + handles.first.writeMessage(null, Collections.<Handle>singletonList(handlesToShare.second), + MessagePipeHandle.WriteFlags.NONE); + assertFalse(handlesToShare.second.isValid()); + ResultAnd<MessagePipeHandle.ReadMessageResult> readMessageResult = + handles.second.readMessage(null, 1, MessagePipeHandle.ReadFlags.NONE); + assertEquals(1, readMessageResult.getValue().getHandlesCount()); + MessagePipeHandle newHandle = + readMessageResult.getValue().getHandles().get(0).toMessagePipeHandle(); + addHandleToClose(newHandle); + assertTrue(newHandle.isValid()); + checkSendingMessage(handlesToShare.first, newHandle); + checkSendingMessage(newHandle, handlesToShare.first); + } + + private static void createAndCloseDataPipe(DataPipe.CreateOptions options) { + Core core = CoreImpl.getInstance(); + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = + core.createDataPipe(options); + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeCreation() { + // Create datapipe with null options. + createAndCloseDataPipe(null); + DataPipe.CreateOptions options = new DataPipe.CreateOptions(); + // Create datapipe with element size set. + options.setElementNumBytes(24); + createAndCloseDataPipe(options); + // Create datapipe with capacity set. + options.setCapacityNumBytes(1024 * options.getElementNumBytes()); + createAndCloseDataPipe(options); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeSend() { + Core core = CoreImpl.getInstance(); + + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + checkSendingData(handles.first, handles.second); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeTwoPhaseSend() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = handles.first.beginWriteData(bytes.length, DataPipe.WriteFlags.NONE); + assertTrue(buffer.capacity() >= bytes.length); + buffer.put(bytes); + handles.first.endWriteData(bytes.length); + + // Read into a buffer. + ByteBuffer receiveBuffer = + handles.second.beginReadData(bytes.length, DataPipe.ReadFlags.NONE); + assertEquals(0, receiveBuffer.position()); + assertEquals(bytes.length, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals(bytes, receivedBytes)); + handles.second.endReadData(bytes.length); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeDiscard() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + ResultAnd<Integer> result = handles.first.writeData(buffer, DataPipe.WriteFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().intValue()); + + // Discard bytes. + final int nbBytesToDiscard = 4; + assertEquals(nbBytesToDiscard, + handles.second.discardData(nbBytesToDiscard, DataPipe.ReadFlags.NONE)); + + // Read into a buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length - nbBytesToDiscard); + ResultAnd<Integer> readResult = + handles.second.readData(receiveBuffer, DataPipe.ReadFlags.NONE); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length - nbBytesToDiscard, readResult.getValue().intValue()); + assertEquals(0, receiveBuffer.position()); + assertEquals(bytes.length - nbBytesToDiscard, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length - nbBytesToDiscard]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals( + Arrays.copyOfRange(bytes, nbBytesToDiscard, bytes.length), receivedBytes)); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @SmallTest + public void testSharedBufferCreation() { + Core core = CoreImpl.getInstance(); + // Test creation with empty options. + core.createSharedBuffer(null, 8).close(); + // Test creation with default options. + core.createSharedBuffer(new SharedBufferHandle.CreateOptions(), 8).close(); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @SmallTest + public void testSharedBufferDuplication() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + + // Test duplication with empty options. + handle.duplicate(null).close(); + // Test creation with default options. + handle.duplicate(new SharedBufferHandle.DuplicateOptions()).close(); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @SmallTest + public void testSharedBufferSending() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + SharedBufferHandle newHandle = handle.duplicate(null); + addHandleToClose(newHandle); + + checkSharing(handle, newHandle); + checkSharing(newHandle, handle); + } + + /** + * Testing that invalid handle can be used with this implementation. + */ + @SmallTest + public void testInvalidHandle() { + Core core = CoreImpl.getInstance(); + Handle handle = InvalidHandle.INSTANCE; + + // Checking sending an invalid handle. + // Until the behavior is changed on the C++ side, handle gracefully 2 different use case: + // - Receive a INVALID_ARGUMENT exception + // - Receive an invalid handle on the other side. + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + try { + handles.first.writeMessage(null, Collections.<Handle>singletonList(handle), + MessagePipeHandle.WriteFlags.NONE); + ResultAnd<MessagePipeHandle.ReadMessageResult> readMessageResult = + handles.second.readMessage(null, 1, MessagePipeHandle.ReadFlags.NONE); + assertEquals(1, readMessageResult.getValue().getHandlesCount()); + assertFalse(readMessageResult.getValue().getHandles().get(0).isValid()); + } catch (MojoException e) { + assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult()); + } + } + + /** + * Testing the pass method on message pipes. + */ + @SmallTest + public void testMessagePipeHandlePass() { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + assertTrue(handles.first.isValid()); + MessagePipeHandle handleClone = handles.first.pass(); + + addHandleToClose(handleClone); + + assertFalse(handles.first.isValid()); + assertTrue(handleClone.isValid()); + checkSendingMessage(handleClone, handles.second); + checkSendingMessage(handles.second, handleClone); + } + + /** + * Testing the pass method on data pipes. + */ + @SmallTest + public void testDataPipeHandlePass() { + Core core = CoreImpl.getInstance(); + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + DataPipe.ProducerHandle producerClone = handles.first.pass(); + DataPipe.ConsumerHandle consumerClone = handles.second.pass(); + + addHandleToClose(producerClone); + addHandleToClose(consumerClone); + + assertFalse(handles.first.isValid()); + assertFalse(handles.second.isValid()); + assertTrue(producerClone.isValid()); + assertTrue(consumerClone.isValid()); + checkSendingData(producerClone, consumerClone); + } + + /** + * Testing the pass method on shared buffers. + */ + @SmallTest + public void testSharedBufferPass() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + SharedBufferHandle newHandle = handle.duplicate(null); + addHandleToClose(newHandle); + + SharedBufferHandle handleClone = handle.pass(); + SharedBufferHandle newHandleClone = newHandle.pass(); + + addHandleToClose(handleClone); + addHandleToClose(newHandleClone); + + assertFalse(handle.isValid()); + assertTrue(handleClone.isValid()); + checkSharing(handleClone, newHandleClone); + checkSharing(newHandleClone, handleClone); + } + + /** + * esting handle conversion to native and back. + */ + @SmallTest + public void testHandleConversion() { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + MessagePipeHandle converted = + core.acquireNativeHandle(handles.first.releaseNativeHandle()).toMessagePipeHandle(); + addHandleToClose(converted); + + assertFalse(handles.first.isValid()); + + checkSendingMessage(converted, handles.second); + checkSendingMessage(handles.second, converted); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java new file mode 100644 index 0000000000..e14adb1160 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java @@ -0,0 +1,255 @@ +// Copyright 2016 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.system.impl; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.InvalidHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.Watcher; +import org.chromium.mojo.system.Watcher.Callback; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Testing the Watcher. + */ +public class WatcherImplTest extends MojoTestCase { + private List<Handle> mHandlesToClose = new ArrayList<Handle>(); + private Watcher mWatcher; + private Core mCore; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + mWatcher = new WatcherImpl(); + mCore = CoreImpl.getInstance(); + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + mWatcher.destroy(); + MojoException toThrow = null; + for (Handle handle : mHandlesToClose) { + try { + handle.close(); + } catch (MojoException e) { + if (toThrow == null) { + toThrow = e; + } + } + } + if (toThrow != null) { + throw toThrow; + } + super.tearDown(); + } + + private void addHandlePairToClose(Pair<? extends Handle, ? extends Handle> handles) { + mHandlesToClose.add(handles.first); + mHandlesToClose.add(handles.second); + } + + private static class WatcherResult implements Callback { + private int mResult = Integer.MIN_VALUE; + private MessagePipeHandle mReadPipe; + + /** + * @param readPipe A MessagePipeHandle to read from when onResult triggers success. + */ + public WatcherResult(MessagePipeHandle readPipe) { + mReadPipe = readPipe; + } + public WatcherResult() { + this(null); + } + + /** + * @see Callback#onResult(int) + */ + @Override + public void onResult(int result) { + this.mResult = result; + + if (result == MojoResult.OK && mReadPipe != null) { + mReadPipe.readMessage( + null, 0, MessagePipeHandle.ReadFlags.none().setMayDiscard(true)); + } + } + + /** + * @return the result + */ + public int getResult() { + return mResult; + } + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testCorrectResult() { + // Checking a correct result. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + final WatcherResult watcherResult = new WatcherResult(handles.first); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.writeMessage( + ByteBuffer.allocateDirect(1), null, MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + assertEquals(MojoResult.OK, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testClosingPeerHandle() { + // Closing the peer handle. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.close(); + runLoopUntilIdle(); + assertEquals(MojoResult.FAILED_PRECONDITION, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testClosingWatchedHandle() { + // Closing the peer handle. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.first.close(); + runLoopUntilIdle(); + assertEquals(MojoResult.CANCELLED, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testInvalidHandle() { + // Closing the peer handle. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.first.close(); + assertEquals(MojoResult.INVALID_ARGUMENT, + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult)); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testDefaultInvalidHandle() { + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + assertEquals(MojoResult.INVALID_ARGUMENT, + mWatcher.start(InvalidHandle.INSTANCE, Core.HandleSignals.READABLE, watcherResult)); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testCancel() { + // Closing the peer handle. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.cancel(); + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.writeMessage( + ByteBuffer.allocateDirect(1), null, MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testImmediateCancelOnInvalidHandle() { + // Closing the peer handle. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + handles.first.close(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + mWatcher.cancel(); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } +} diff --git a/mojo/android/javatests/validation_test_util.cc b/mojo/android/javatests/validation_test_util.cc new file mode 100644 index 0000000000..75f79b370e --- /dev/null +++ b/mojo/android/javatests/validation_test_util.cc @@ -0,0 +1,54 @@ +// 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. + +#include "mojo/android/javatests/validation_test_util.h" + +#include <stddef.h> +#include <stdint.h> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/test/test_support_android.h" +#include "jni/ValidationTestUtil_jni.h" +#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h" + +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; + +namespace mojo { +namespace android { + +bool RegisterValidationTestUtil(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +ScopedJavaLocalRef<jobject> ParseData( + JNIEnv* env, + const JavaParamRef<jclass>& jcaller, + const JavaParamRef<jstring>& data_as_string) { + std::string input = + base::android::ConvertJavaStringToUTF8(env, data_as_string); + std::vector<uint8_t> data; + size_t num_handles; + std::string error_message; + if (!test::ParseValidationTestInput( + input, &data, &num_handles, &error_message)) { + ScopedJavaLocalRef<jstring> j_error_message = + base::android::ConvertUTF8ToJavaString(env, error_message); + return Java_ValidationTestUtil_buildData(env, nullptr, 0, j_error_message); + } + void* data_ptr = &data[0]; + if (!data_ptr) { + DCHECK(!data.size()); + data_ptr = &data; + } + jobject byte_buffer = + env->NewDirectByteBuffer(data_ptr, data.size()); + return Java_ValidationTestUtil_buildData(env, byte_buffer, num_handles, + nullptr); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/javatests/validation_test_util.h b/mojo/android/javatests/validation_test_util.h new file mode 100644 index 0000000000..f58dc07885 --- /dev/null +++ b/mojo/android/javatests/validation_test_util.h @@ -0,0 +1,20 @@ +// 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. + +#ifndef MOJO_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_ +#define MOJO_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterValidationTestUtil(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_SYSTEM_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_ diff --git a/mojo/android/system/base_run_loop.cc b/mojo/android/system/base_run_loop.cc new file mode 100644 index 0000000000..7993ba86a3 --- /dev/null +++ b/mojo/android/system/base_run_loop.cc @@ -0,0 +1,82 @@ +// Copyright 2015 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. + +#include "mojo/android/system/base_run_loop.h" + +#include <jni.h> + +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +// #include "base/android/jni_registrar.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "jni/BaseRunLoop_jni.h" + +using base::android::JavaParamRef; + +namespace mojo { +namespace android { + +static jlong CreateBaseRunLoop(JNIEnv* env, + const JavaParamRef<jobject>& jcaller) { + base::MessageLoop* message_loop = new base::MessageLoop; + return reinterpret_cast<uintptr_t>(message_loop); +} + +static void Run(JNIEnv* env, + const JavaParamRef<jobject>& jcaller) { + base::RunLoop().Run(); +} + +static void RunUntilIdle(JNIEnv* env, + const JavaParamRef<jobject>& jcaller) { + base::RunLoop().RunUntilIdle(); +} + +static void Quit(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jlong runLoopID) { + reinterpret_cast<base::MessageLoop*>(runLoopID)->QuitWhenIdle(); +} + +static void RunJavaRunnable( + const base::android::ScopedJavaGlobalRef<jobject>& runnable_ref) { + Java_BaseRunLoop_runRunnable(base::android::AttachCurrentThread(), + runnable_ref); +} + +static void PostDelayedTask(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jlong runLoopID, + const JavaParamRef<jobject>& runnable, + jlong delay) { + base::android::ScopedJavaGlobalRef<jobject> runnable_ref; + // ScopedJavaGlobalRef do not hold onto the env reference, so it is safe to + // use it across threads. |RunJavaRunnable| will acquire a new JNIEnv before + // running the Runnable. + runnable_ref.Reset(env, runnable); + reinterpret_cast<base::MessageLoop*>(runLoopID) + ->task_runner() + ->PostDelayedTask(FROM_HERE, base::Bind(&RunJavaRunnable, runnable_ref), + base::TimeDelta::FromMicroseconds(delay)); +} + +static void DeleteMessageLoop(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jlong runLoopID) { + base::MessageLoop* message_loop = + reinterpret_cast<base::MessageLoop*>(runLoopID); + delete message_loop; +} + +bool RegisterBaseRunLoop(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/system/base_run_loop.h b/mojo/android/system/base_run_loop.h new file mode 100644 index 0000000000..f225c65375 --- /dev/null +++ b/mojo/android/system/base_run_loop.h @@ -0,0 +1,20 @@ +// Copyright 2015 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. + +#ifndef MOJO_ANDROID_SYSTEM_BASE_RUN_LOOP_H_ +#define MOJO_ANDROID_SYSTEM_BASE_RUN_LOOP_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterBaseRunLoop(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_ANDROID_SYSTEM_BASE_RUN_LOOP_H_ diff --git a/mojo/android/system/core_impl.cc b/mojo/android/system/core_impl.cc new file mode 100644 index 0000000000..7d5a40220d --- /dev/null +++ b/mojo/android/system/core_impl.cc @@ -0,0 +1,310 @@ +// 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. + +#include "mojo/android/system/core_impl.h" + +#include <stddef.h> +#include <stdint.h> + +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +// #include "base/android/jni_registrar.h" +// #include "base/android/library_loader/library_loader_hooks.h" +#include "base/android/scoped_java_ref.h" +#include "jni/CoreImpl_jni.h" +#include "mojo/public/c/system/core.h" + +namespace mojo { +namespace android { + +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; + +static jlong GetTimeTicksNow(JNIEnv* env, + const JavaParamRef<jobject>& jcaller) { + return MojoGetTimeTicksNow(); +} + +static ScopedJavaLocalRef<jobject> CreateMessagePipe( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + const JavaParamRef<jobject>& options_buffer) { + const MojoCreateMessagePipeOptions* options = NULL; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateMessagePipeOptions)); + options = static_cast<const MojoCreateMessagePipeOptions*>(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle1; + MojoHandle handle2; + MojoResult result = MojoCreateMessagePipe(options, &handle1, &handle2); + return Java_CoreImpl_newNativeCreationResult(env, result, handle1, handle2); +} + +static ScopedJavaLocalRef<jobject> CreateDataPipe( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + const JavaParamRef<jobject>& options_buffer) { + const MojoCreateDataPipeOptions* options = NULL; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateDataPipeOptions)); + options = static_cast<const MojoCreateDataPipeOptions*>(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle1; + MojoHandle handle2; + MojoResult result = MojoCreateDataPipe(options, &handle1, &handle2); + return Java_CoreImpl_newNativeCreationResult(env, result, handle1, handle2); +} + +static ScopedJavaLocalRef<jobject> CreateSharedBuffer( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + const JavaParamRef<jobject>& options_buffer, + jlong num_bytes) { + const MojoCreateSharedBufferOptions* options = 0; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + DCHECK_EQ(reinterpret_cast<const uintptr_t>(buffer_start) % 8, 0u); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateSharedBufferOptions)); + options = static_cast<const MojoCreateSharedBufferOptions*>(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle; + MojoResult result = MojoCreateSharedBuffer(options, num_bytes, &handle); + return Java_CoreImpl_newResultAndInteger(env, result, handle); +} + +static jint Close(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle) { + return MojoClose(mojo_handle); +} + +static jint QueryHandleSignalsState(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + const JavaParamRef<jobject>& buffer) { + MojoHandleSignalsState* signals_state = + static_cast<MojoHandleSignalsState*>(env->GetDirectBufferAddress(buffer)); + DCHECK(signals_state); + DCHECK_EQ(sizeof(MojoHandleSignalsState), + static_cast<size_t>(env->GetDirectBufferCapacity(buffer))); + return MojoQueryHandleSignalsState(mojo_handle, signals_state); +} + +static jint WriteMessage(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + const JavaParamRef<jobject>& bytes, + jint num_bytes, + const JavaParamRef<jobject>& handles_buffer, + jint flags) { + const void* buffer_start = 0; + uint32_t buffer_size = 0; + if (bytes) { + buffer_start = env->GetDirectBufferAddress(bytes); + DCHECK(buffer_start); + DCHECK(env->GetDirectBufferCapacity(bytes) >= num_bytes); + buffer_size = num_bytes; + } + const MojoHandle* handles = 0; + uint32_t num_handles = 0; + if (handles_buffer) { + handles = + static_cast<MojoHandle*>(env->GetDirectBufferAddress(handles_buffer)); + num_handles = env->GetDirectBufferCapacity(handles_buffer) / 4; + } + // Java code will handle invalidating handles if the write succeeded. + return MojoWriteMessage( + mojo_handle, buffer_start, buffer_size, handles, num_handles, flags); +} + +static ScopedJavaLocalRef<jobject> ReadMessage( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + const JavaParamRef<jobject>& bytes, + const JavaParamRef<jobject>& handles_buffer, + jint flags) { + void* buffer_start = 0; + uint32_t buffer_size = 0; + if (bytes) { + buffer_start = env->GetDirectBufferAddress(bytes); + DCHECK(buffer_start); + buffer_size = env->GetDirectBufferCapacity(bytes); + } + MojoHandle* handles = 0; + uint32_t num_handles = 0; + if (handles_buffer) { + handles = + static_cast<MojoHandle*>(env->GetDirectBufferAddress(handles_buffer)); + num_handles = env->GetDirectBufferCapacity(handles_buffer) / 4; + } + MojoResult result = MojoReadMessage( + mojo_handle, buffer_start, &buffer_size, handles, &num_handles, flags); + // Jave code will handle taking ownership of any received handle. + return Java_CoreImpl_newReadMessageResult(env, result, buffer_size, + num_handles); +} + +static ScopedJavaLocalRef<jobject> ReadData( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + const JavaParamRef<jobject>& elements, + jint elements_capacity, + jint flags) { + void* buffer_start = 0; + uint32_t buffer_size = elements_capacity; + if (elements) { + buffer_start = env->GetDirectBufferAddress(elements); + DCHECK(buffer_start); + DCHECK(elements_capacity <= env->GetDirectBufferCapacity(elements)); + } + MojoResult result = + MojoReadData(mojo_handle, buffer_start, &buffer_size, flags); + return Java_CoreImpl_newResultAndInteger( + env, result, (result == MOJO_RESULT_OK) ? buffer_size : 0); +} + +static ScopedJavaLocalRef<jobject> BeginReadData( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + jint num_bytes, + jint flags) { + void const* buffer = 0; + uint32_t buffer_size = num_bytes; + MojoResult result = + MojoBeginReadData(mojo_handle, &buffer, &buffer_size, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = + env->NewDirectByteBuffer(const_cast<void*>(buffer), buffer_size); + } + return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer); +} + +static jint EndReadData(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + jint num_bytes_read) { + return MojoEndReadData(mojo_handle, num_bytes_read); +} + +static ScopedJavaLocalRef<jobject> WriteData( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + const JavaParamRef<jobject>& elements, + jint limit, + jint flags) { + void* buffer_start = env->GetDirectBufferAddress(elements); + DCHECK(buffer_start); + DCHECK(limit <= env->GetDirectBufferCapacity(elements)); + uint32_t buffer_size = limit; + MojoResult result = + MojoWriteData(mojo_handle, buffer_start, &buffer_size, flags); + return Java_CoreImpl_newResultAndInteger( + env, result, (result == MOJO_RESULT_OK) ? buffer_size : 0); +} + +static ScopedJavaLocalRef<jobject> BeginWriteData( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + jint num_bytes, + jint flags) { + void* buffer = 0; + uint32_t buffer_size = num_bytes; + MojoResult result = + MojoBeginWriteData(mojo_handle, &buffer, &buffer_size, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = env->NewDirectByteBuffer(buffer, buffer_size); + } + return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer); +} + +static jint EndWriteData(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + jint num_bytes_written) { + return MojoEndWriteData(mojo_handle, num_bytes_written); +} + +static ScopedJavaLocalRef<jobject> Duplicate( + JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + const JavaParamRef<jobject>& options_buffer) { + const MojoDuplicateBufferHandleOptions* options = 0; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoDuplicateBufferHandleOptions)); + options = + static_cast<const MojoDuplicateBufferHandleOptions*>(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle; + MojoResult result = MojoDuplicateBufferHandle(mojo_handle, options, &handle); + return Java_CoreImpl_newResultAndInteger(env, result, handle); +} + +static ScopedJavaLocalRef<jobject> Map(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + jlong offset, + jlong num_bytes, + jint flags) { + void* buffer = 0; + MojoResult result = + MojoMapBuffer(mojo_handle, offset, num_bytes, &buffer, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = env->NewDirectByteBuffer(buffer, num_bytes); + } + return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer); +} + +static int Unmap(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + const JavaParamRef<jobject>& buffer) { + void* buffer_start = env->GetDirectBufferAddress(buffer); + DCHECK(buffer_start); + return MojoUnmapBuffer(buffer_start); +} + +static jint GetNativeBufferOffset(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + const JavaParamRef<jobject>& buffer, + jint alignment) { + jint offset = + reinterpret_cast<uintptr_t>(env->GetDirectBufferAddress(buffer)) % + alignment; + if (offset == 0) + return 0; + return alignment - offset; +} + +bool RegisterCoreImpl(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/system/core_impl.h b/mojo/android/system/core_impl.h new file mode 100644 index 0000000000..c6249994e5 --- /dev/null +++ b/mojo/android/system/core_impl.h @@ -0,0 +1,20 @@ +// 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. + +#ifndef MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ +#define MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterCoreImpl(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java b/mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java new file mode 100644 index 0000000000..3db6670d71 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java @@ -0,0 +1,74 @@ +// Copyright 2015 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.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.mojo.system.RunLoop; + +/** + * Implementation of {@link RunLoop} suitable for the base:: message loop implementation. + */ +@JNINamespace("mojo::android") +class BaseRunLoop implements RunLoop { + /** + * Pointer to the C run loop. + */ + private long mRunLoopID; + private final CoreImpl mCore; + + BaseRunLoop(CoreImpl core) { + this.mCore = core; + this.mRunLoopID = nativeCreateBaseRunLoop(); + } + + @Override + public void run() { + assert mRunLoopID != 0 : "The run loop cannot run once closed"; + nativeRun(); + } + + @Override + public void runUntilIdle() { + assert mRunLoopID != 0 : "The run loop cannot run once closed"; + nativeRunUntilIdle(); + } + + @Override + public void quit() { + assert mRunLoopID != 0 : "The run loop cannot be quitted run once closed"; + nativeQuit(mRunLoopID); + } + + @Override + public void postDelayedTask(Runnable runnable, long delay) { + assert mRunLoopID != 0 : "The run loop cannot run tasks once closed"; + nativePostDelayedTask(mRunLoopID, runnable, delay); + } + + @Override + public void close() { + if (mRunLoopID == 0) { + return; + } + // We don't want to de-register a different run loop! + assert mCore.getCurrentRunLoop() == this : "Only the current run loop can be closed"; + mCore.clearCurrentRunLoop(); + nativeDeleteMessageLoop(mRunLoopID); + mRunLoopID = 0; + } + + @CalledByNative + private static void runRunnable(Runnable runnable) { + runnable.run(); + } + + private native long nativeCreateBaseRunLoop(); + private native void nativeRun(); + private native void nativeRunUntilIdle(); + private native void nativeQuit(long runLoopID); + private native void nativePostDelayedTask(long runLoopID, Runnable runnable, long delay); + private native void nativeDeleteMessageLoop(long runLoopID); +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java new file mode 100644 index 0000000000..173f80180f --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java @@ -0,0 +1,522 @@ +// 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.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.RunLoop; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.SharedBufferHandle.DuplicateOptions; +import org.chromium.mojo.system.SharedBufferHandle.MapFlags; +import org.chromium.mojo.system.UntypedHandle; +import org.chromium.mojo.system.Watcher; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of {@link Core}. + */ +@JNINamespace("mojo::android") +@MainDex +public class CoreImpl implements Core { + /** + * Discard flag for the |MojoReadData| operation. + */ + private static final int MOJO_READ_DATA_FLAG_DISCARD = 1 << 1; + + /** + * the size of a handle, in bytes. + */ + private static final int HANDLE_SIZE = 4; + + /** + * the size of a flag, in bytes. + */ + private static final int FLAG_SIZE = 4; + + /** + * The mojo handle for an invalid handle. + */ + static final int INVALID_HANDLE = 0; + + private static class LazyHolder { private static final Core INSTANCE = new CoreImpl(); } + + /** + * The run loop for the current thread. + */ + private final ThreadLocal<BaseRunLoop> mCurrentRunLoop = new ThreadLocal<BaseRunLoop>(); + + /** + * The offset needed to get an aligned buffer. + */ + private final int mByteBufferOffset; + + /** + * @return the instance. + */ + public static Core getInstance() { + return LazyHolder.INSTANCE; + } + + private CoreImpl() { + // Fix for the ART runtime, before: + // https://android.googlesource.com/platform/libcore/+/fb6c80875a8a8d0a9628562f89c250b6a962e824%5E!/ + // This assumes consistent allocation. + mByteBufferOffset = nativeGetNativeBufferOffset(ByteBuffer.allocateDirect(8), 8); + } + + /** + * @see Core#getTimeTicksNow() + */ + @Override + public long getTimeTicksNow() { + return nativeGetTimeTicksNow(); + } + + /** + * @see Core#createMessagePipe(MessagePipeHandle.CreateOptions) + */ + @Override + public Pair<MessagePipeHandle, MessagePipeHandle> createMessagePipe( + MessagePipeHandle.CreateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd<IntegerPair> result = nativeCreateMessagePipe(optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return Pair.<MessagePipeHandle, MessagePipeHandle>create( + new MessagePipeHandleImpl(this, result.getValue().first), + new MessagePipeHandleImpl(this, result.getValue().second)); + } + + /** + * @see Core#createDataPipe(DataPipe.CreateOptions) + */ + @Override + public Pair<ProducerHandle, ConsumerHandle> createDataPipe(DataPipe.CreateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(16); + optionsBuffer.putInt(0, 16); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + optionsBuffer.putInt(8, options.getElementNumBytes()); + optionsBuffer.putInt(12, options.getCapacityNumBytes()); + } + ResultAnd<IntegerPair> result = nativeCreateDataPipe(optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return Pair.<ProducerHandle, ConsumerHandle>create( + new DataPipeProducerHandleImpl(this, result.getValue().first), + new DataPipeConsumerHandleImpl(this, result.getValue().second)); + } + + /** + * @see Core#createSharedBuffer(SharedBufferHandle.CreateOptions, long) + */ + @Override + public SharedBufferHandle createSharedBuffer( + SharedBufferHandle.CreateOptions options, long numBytes) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd<Integer> result = nativeCreateSharedBuffer(optionsBuffer, numBytes); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return new SharedBufferHandleImpl(this, result.getValue()); + } + + /** + * @see org.chromium.mojo.system.Core#acquireNativeHandle(int) + */ + @Override + public UntypedHandle acquireNativeHandle(int handle) { + return new UntypedHandleImpl(this, handle); + } + + /** + * @see Core#getWatcher() + */ + @Override + public Watcher getWatcher() { + return new WatcherImpl(); + } + + /** + * @see Core#createDefaultRunLoop() + */ + @Override + public RunLoop createDefaultRunLoop() { + if (mCurrentRunLoop.get() != null) { + throw new MojoException(MojoResult.FAILED_PRECONDITION); + } + BaseRunLoop runLoop = new BaseRunLoop(this); + mCurrentRunLoop.set(runLoop); + return runLoop; + } + + /** + * @see Core#getCurrentRunLoop() + */ + @Override + public RunLoop getCurrentRunLoop() { + return mCurrentRunLoop.get(); + } + + /** + * Remove the current run loop. + */ + void clearCurrentRunLoop() { + mCurrentRunLoop.remove(); + } + + int closeWithResult(int mojoHandle) { + return nativeClose(mojoHandle); + } + + void close(int mojoHandle) { + int mojoResult = nativeClose(mojoHandle); + if (mojoResult != MojoResult.OK) { + throw new MojoException(mojoResult); + } + } + + HandleSignalsState queryHandleSignalsState(int mojoHandle) { + ByteBuffer buffer = allocateDirectBuffer(8); + int result = nativeQueryHandleSignalsState(mojoHandle, buffer); + if (result != MojoResult.OK) throw new MojoException(result); + return new HandleSignalsState( + new HandleSignals(buffer.getInt(0)), new HandleSignals(buffer.getInt(4))); + } + + /** + * @see MessagePipeHandle#writeMessage(ByteBuffer, List, MessagePipeHandle.WriteFlags) + */ + void writeMessage(MessagePipeHandleImpl pipeHandle, ByteBuffer bytes, + List<? extends Handle> handles, MessagePipeHandle.WriteFlags flags) { + ByteBuffer handlesBuffer = null; + if (handles != null && !handles.isEmpty()) { + handlesBuffer = allocateDirectBuffer(handles.size() * HANDLE_SIZE); + for (Handle handle : handles) { + handlesBuffer.putInt(getMojoHandle(handle)); + } + handlesBuffer.position(0); + } + int mojoResult = nativeWriteMessage(pipeHandle.getMojoHandle(), bytes, + bytes == null ? 0 : bytes.limit(), handlesBuffer, flags.getFlags()); + if (mojoResult != MojoResult.OK) { + throw new MojoException(mojoResult); + } + // Success means the handles have been invalidated. + if (handles != null) { + for (Handle handle : handles) { + if (handle.isValid()) { + ((HandleBase) handle).invalidateHandle(); + } + } + } + } + + /** + * @see MessagePipeHandle#readMessage(ByteBuffer, int, MessagePipeHandle.ReadFlags) + */ + ResultAnd<MessagePipeHandle.ReadMessageResult> readMessage(MessagePipeHandleImpl handle, + ByteBuffer bytes, int maxNumberOfHandles, MessagePipeHandle.ReadFlags flags) { + ByteBuffer handlesBuffer = null; + if (maxNumberOfHandles > 0) { + handlesBuffer = allocateDirectBuffer(maxNumberOfHandles * HANDLE_SIZE); + } + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + nativeReadMessage(handle.getMojoHandle(), bytes, handlesBuffer, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK + && result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED + && result.getMojoResult() != MojoResult.SHOULD_WAIT) { + throw new MojoException(result.getMojoResult()); + } + + if (result.getMojoResult() == MojoResult.OK) { + MessagePipeHandle.ReadMessageResult readResult = result.getValue(); + if (bytes != null) { + bytes.position(0); + bytes.limit(readResult.getMessageSize()); + } + + List<UntypedHandle> handles = + new ArrayList<UntypedHandle>(readResult.getHandlesCount()); + for (int i = 0; i < readResult.getHandlesCount(); ++i) { + int mojoHandle = handlesBuffer.getInt(HANDLE_SIZE * i); + handles.add(new UntypedHandleImpl(this, mojoHandle)); + } + readResult.setHandles(handles); + } + return result; + } + + /** + * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags) + */ + int discardData(DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) { + ResultAnd<Integer> result = nativeReadData(handle.getMojoHandle(), null, numBytes, + flags.getFlags() | MOJO_READ_DATA_FLAG_DISCARD); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see ConsumerHandle#readData(ByteBuffer, DataPipe.ReadFlags) + */ + ResultAnd<Integer> readData( + DataPipeConsumerHandleImpl handle, ByteBuffer elements, DataPipe.ReadFlags flags) { + ResultAnd<Integer> result = nativeReadData(handle.getMojoHandle(), elements, + elements == null ? 0 : elements.capacity(), flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK + && result.getMojoResult() != MojoResult.SHOULD_WAIT) { + throw new MojoException(result.getMojoResult()); + } + if (result.getMojoResult() == MojoResult.OK) { + if (elements != null) { + elements.limit(result.getValue()); + } + } + return result; + } + + /** + * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags) + */ + ByteBuffer beginReadData( + DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) { + ResultAnd<ByteBuffer> result = + nativeBeginReadData(handle.getMojoHandle(), numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue().asReadOnlyBuffer(); + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + void endReadData(DataPipeConsumerHandleImpl handle, int numBytesRead) { + int result = nativeEndReadData(handle.getMojoHandle(), numBytesRead); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @see ProducerHandle#writeData(ByteBuffer, DataPipe.WriteFlags) + */ + ResultAnd<Integer> writeData( + DataPipeProducerHandleImpl handle, ByteBuffer elements, DataPipe.WriteFlags flags) { + return nativeWriteData( + handle.getMojoHandle(), elements, elements.limit(), flags.getFlags()); + } + + /** + * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags) + */ + ByteBuffer beginWriteData( + DataPipeProducerHandleImpl handle, int numBytes, DataPipe.WriteFlags flags) { + ResultAnd<ByteBuffer> result = + nativeBeginWriteData(handle.getMojoHandle(), numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + void endWriteData(DataPipeProducerHandleImpl handle, int numBytesWritten) { + int result = nativeEndWriteData(handle.getMojoHandle(), numBytesWritten); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @see SharedBufferHandle#duplicate(DuplicateOptions) + */ + SharedBufferHandle duplicate(SharedBufferHandleImpl handle, DuplicateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd<Integer> result = nativeDuplicate(handle.getMojoHandle(), optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return new SharedBufferHandleImpl(this, result.getValue()); + } + + /** + * @see SharedBufferHandle#map(long, long, MapFlags) + */ + ByteBuffer map(SharedBufferHandleImpl handle, long offset, long numBytes, MapFlags flags) { + ResultAnd<ByteBuffer> result = + nativeMap(handle.getMojoHandle(), offset, numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see SharedBufferHandle#unmap(ByteBuffer) + */ + void unmap(ByteBuffer buffer) { + int result = nativeUnmap(buffer); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @return the mojo handle associated to the given handle, considering invalid handles. + */ + private int getMojoHandle(Handle handle) { + if (handle.isValid()) { + return ((HandleBase) handle).getMojoHandle(); + } + return 0; + } + + private static boolean isUnrecoverableError(int code) { + switch (code) { + case MojoResult.OK: + case MojoResult.DEADLINE_EXCEEDED: + case MojoResult.CANCELLED: + case MojoResult.FAILED_PRECONDITION: + return false; + default: + return true; + } + } + + private static int filterMojoResultForWait(int code) { + if (isUnrecoverableError(code)) { + throw new MojoException(code); + } + return code; + } + + private ByteBuffer allocateDirectBuffer(int capacity) { + ByteBuffer buffer = ByteBuffer.allocateDirect(capacity + mByteBufferOffset); + if (mByteBufferOffset != 0) { + buffer.position(mByteBufferOffset); + buffer = buffer.slice(); + } + return buffer.order(ByteOrder.nativeOrder()); + } + + @CalledByNative + private static ResultAnd<ByteBuffer> newResultAndBuffer(int mojoResult, ByteBuffer buffer) { + return new ResultAnd<>(mojoResult, buffer); + } + + /** + * Trivial alias for Pair<Integer, Integer>. This is needed because our jni generator is unable + * to handle class that contains space. + */ + private static final class IntegerPair extends Pair<Integer, Integer> { + public IntegerPair(Integer first, Integer second) { + super(first, second); + } + } + + @CalledByNative + private static ResultAnd<MessagePipeHandle.ReadMessageResult> newReadMessageResult( + int mojoResult, int messageSize, int handlesCount) { + MessagePipeHandle.ReadMessageResult result = new MessagePipeHandle.ReadMessageResult(); + result.setMessageSize(messageSize); + result.setHandlesCount(handlesCount); + return new ResultAnd<>(mojoResult, result); + } + + @CalledByNative + private static ResultAnd<Integer> newResultAndInteger(int mojoResult, int numBytesRead) { + return new ResultAnd<>(mojoResult, numBytesRead); + } + + @CalledByNative + private static ResultAnd<IntegerPair> newNativeCreationResult( + int mojoResult, int mojoHandle1, int mojoHandle2) { + return new ResultAnd<>(mojoResult, new IntegerPair(mojoHandle1, mojoHandle2)); + } + + private native long nativeGetTimeTicksNow(); + + private native ResultAnd<IntegerPair> nativeCreateMessagePipe(ByteBuffer optionsBuffer); + + private native ResultAnd<IntegerPair> nativeCreateDataPipe(ByteBuffer optionsBuffer); + + private native ResultAnd<Integer> nativeCreateSharedBuffer( + ByteBuffer optionsBuffer, long numBytes); + + private native int nativeClose(int mojoHandle); + + private native int nativeQueryHandleSignalsState(int mojoHandle, ByteBuffer signalsStateBuffer); + + private native int nativeWriteMessage( + int mojoHandle, ByteBuffer bytes, int numBytes, ByteBuffer handlesBuffer, int flags); + + private native ResultAnd<MessagePipeHandle.ReadMessageResult> nativeReadMessage( + int mojoHandle, ByteBuffer bytes, ByteBuffer handlesBuffer, int flags); + + private native ResultAnd<Integer> nativeReadData( + int mojoHandle, ByteBuffer elements, int elementsSize, int flags); + + private native ResultAnd<ByteBuffer> nativeBeginReadData( + int mojoHandle, int numBytes, int flags); + + private native int nativeEndReadData(int mojoHandle, int numBytesRead); + + private native ResultAnd<Integer> nativeWriteData( + int mojoHandle, ByteBuffer elements, int limit, int flags); + + private native ResultAnd<ByteBuffer> nativeBeginWriteData( + int mojoHandle, int numBytes, int flags); + + private native int nativeEndWriteData(int mojoHandle, int numBytesWritten); + + private native ResultAnd<Integer> nativeDuplicate(int mojoHandle, ByteBuffer optionsBuffer); + + private native ResultAnd<ByteBuffer> nativeMap( + int mojoHandle, long offset, long numBytes, int flags); + + private native int nativeUnmap(ByteBuffer buffer); + + private native int nativeGetNativeBufferOffset(ByteBuffer buffer, int alignment); +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java new file mode 100644 index 0000000000..83097d7eb4 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java @@ -0,0 +1,72 @@ +// 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.system.impl; + +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ReadFlags; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link ConsumerHandle}. + */ +class DataPipeConsumerHandleImpl extends HandleBase implements ConsumerHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + DataPipeConsumerHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + DataPipeConsumerHandleImpl(HandleBase other) { + super(other); + } + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public ConsumerHandle pass() { + return new DataPipeConsumerHandleImpl(this); + } + + /** + * @see ConsumerHandle#discardData(int, ReadFlags) + */ + @Override + public int discardData(int numBytes, ReadFlags flags) { + return mCore.discardData(this, numBytes, flags); + } + + /** + * @see ConsumerHandle#readData(ByteBuffer, ReadFlags) + */ + @Override + public ResultAnd<Integer> readData(ByteBuffer elements, ReadFlags flags) { + return mCore.readData(this, elements, flags); + } + + /** + * @see ConsumerHandle#beginReadData(int, ReadFlags) + */ + @Override + public ByteBuffer beginReadData(int numBytes, ReadFlags flags) { + return mCore.beginReadData(this, numBytes, flags); + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + @Override + public void endReadData(int numBytesRead) { + mCore.endReadData(this, numBytesRead); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java new file mode 100644 index 0000000000..901f26c029 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java @@ -0,0 +1,64 @@ +// 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.system.impl; + +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.DataPipe.WriteFlags; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link ProducerHandle}. + */ +class DataPipeProducerHandleImpl extends HandleBase implements ProducerHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + DataPipeProducerHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + DataPipeProducerHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.DataPipe.ProducerHandle#pass() + */ + @Override + public ProducerHandle pass() { + return new DataPipeProducerHandleImpl(this); + } + + /** + * @see ProducerHandle#writeData(ByteBuffer, WriteFlags) + */ + @Override + public ResultAnd<Integer> writeData(ByteBuffer elements, WriteFlags flags) { + return mCore.writeData(this, elements, flags); + } + + /** + * @see ProducerHandle#beginWriteData(int, WriteFlags) + */ + @Override + public ByteBuffer beginWriteData(int numBytes, WriteFlags flags) { + return mCore.beginWriteData(this, numBytes, flags); + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + @Override + public void endWriteData(int numBytesWritten) { + mCore.endWriteData(this, numBytesWritten); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java new file mode 100644 index 0000000000..4d149a48d7 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java @@ -0,0 +1,140 @@ +// 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.system.impl; + +import android.util.Log; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.UntypedHandle; + +/** + * Implementation of {@link Handle}. + */ +abstract class HandleBase implements Handle { + + private static final String TAG = "HandleImpl"; + + /** + * The pointer to the scoped handle owned by this object. + */ + private int mMojoHandle; + + /** + * The core implementation. Will be used to delegate all behavior. + */ + protected CoreImpl mCore; + + /** + * Base constructor. Takes ownership of the passed handle. + */ + HandleBase(CoreImpl core, int mojoHandle) { + mCore = core; + mMojoHandle = mojoHandle; + } + + /** + * Constructor for transforming {@link HandleBase} into a specific one. It is used to transform + * an {@link UntypedHandle} into a typed one, or any handle into an {@link UntypedHandle}. + */ + protected HandleBase(HandleBase other) { + mCore = other.mCore; + HandleBase otherAsHandleImpl = other; + int mojoHandle = otherAsHandleImpl.mMojoHandle; + otherAsHandleImpl.mMojoHandle = CoreImpl.INVALID_HANDLE; + mMojoHandle = mojoHandle; + } + + /** + * @see org.chromium.mojo.system.Handle#close() + */ + @Override + public void close() { + if (mMojoHandle != CoreImpl.INVALID_HANDLE) { + // After a close, the handle is invalid whether the close succeed or not. + int handle = mMojoHandle; + mMojoHandle = CoreImpl.INVALID_HANDLE; + mCore.close(handle); + } + } + + /** + * @see org.chromium.mojo.system.Handle#querySignalsState() + */ + @Override + public HandleSignalsState querySignalsState() { + return mCore.queryHandleSignalsState(mMojoHandle); + } + + /** + * @see org.chromium.mojo.system.Handle#isValid() + */ + @Override + public boolean isValid() { + return mMojoHandle != CoreImpl.INVALID_HANDLE; + } + + /** + * @see org.chromium.mojo.system.Handle#toUntypedHandle() + */ + @Override + public UntypedHandle toUntypedHandle() { + return new UntypedHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.Handle#getCore() + */ + @Override + public Core getCore() { + return mCore; + } + + /** + * @see Handle#releaseNativeHandle() + */ + @Override + public int releaseNativeHandle() { + int result = mMojoHandle; + mMojoHandle = CoreImpl.INVALID_HANDLE; + return result; + } + + /** + * Getter for the native scoped handle. + * + * @return the native scoped handle. + */ + int getMojoHandle() { + return mMojoHandle; + } + + /** + * invalidate the handle. The caller must ensures that the handle does not leak. + */ + void invalidateHandle() { + mMojoHandle = CoreImpl.INVALID_HANDLE; + } + + /** + * Close the handle if it is valid. Necessary because we cannot let handle leak, and we cannot + * ensure that every handle will be manually closed. + * + * @see java.lang.Object#finalize() + */ + @Override + protected final void finalize() throws Throwable { + if (isValid()) { + // This should not happen, as the user of this class should close the handle. Adding a + // warning. + Log.w(TAG, "Handle was not closed."); + // Ignore result at this point. + mCore.closeWithResult(mMojoHandle); + } + super.finalize(); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java new file mode 100644 index 0000000000..b3df0aed5b --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java @@ -0,0 +1,58 @@ +// 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.system.impl; + +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Implementation of {@link MessagePipeHandle}. + */ +class MessagePipeHandleImpl extends HandleBase implements MessagePipeHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + MessagePipeHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + MessagePipeHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.MessagePipeHandle#pass() + */ + @Override + public MessagePipeHandle pass() { + return new MessagePipeHandleImpl(this); + } + + /** + * @see MessagePipeHandle#writeMessage(ByteBuffer, List, WriteFlags) + */ + @Override + public void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags) { + mCore.writeMessage(this, bytes, handles, flags); + } + + /** + * @see MessagePipeHandle#readMessage(ByteBuffer, int, ReadFlags) + */ + @Override + public ResultAnd<ReadMessageResult> readMessage( + ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) { + return mCore.readMessage(this, bytes, maxNumberOfHandles, flags); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java new file mode 100644 index 0000000000..76ef73945f --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java @@ -0,0 +1,62 @@ +// 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.system.impl; + +import org.chromium.mojo.system.SharedBufferHandle; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link SharedBufferHandle}. + */ +class SharedBufferHandleImpl extends HandleBase implements SharedBufferHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + SharedBufferHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + SharedBufferHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.SharedBufferHandle#pass() + */ + @Override + public SharedBufferHandle pass() { + return new SharedBufferHandleImpl(this); + } + + /** + * @see SharedBufferHandle#duplicate(DuplicateOptions) + */ + @Override + public SharedBufferHandle duplicate(DuplicateOptions options) { + return mCore.duplicate(this, options); + } + + /** + * @see SharedBufferHandle#map(long, long, MapFlags) + */ + @Override + public ByteBuffer map(long offset, long numBytes, MapFlags flags) { + return mCore.map(this, offset, numBytes, flags); + } + + /** + * @see SharedBufferHandle#unmap(ByteBuffer) + */ + @Override + public void unmap(ByteBuffer buffer) { + mCore.unmap(buffer); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java new file mode 100644 index 0000000000..4774ab86e9 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java @@ -0,0 +1,72 @@ +// 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.system.impl; + +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.UntypedHandle; + +/** + * Implementation of {@link UntypedHandle}. + */ +class UntypedHandleImpl extends HandleBase implements UntypedHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + UntypedHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + UntypedHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#pass() + */ + @Override + public UntypedHandle pass() { + return new UntypedHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toMessagePipeHandle() + */ + @Override + public MessagePipeHandle toMessagePipeHandle() { + return new MessagePipeHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toDataPipeConsumerHandle() + */ + @Override + public ConsumerHandle toDataPipeConsumerHandle() { + return new DataPipeConsumerHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toDataPipeProducerHandle() + */ + @Override + public ProducerHandle toDataPipeProducerHandle() { + return new DataPipeProducerHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toSharedBufferHandle() + */ + @Override + public SharedBufferHandle toSharedBufferHandle() { + return new SharedBufferHandleImpl(this); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java new file mode 100644 index 0000000000..094ad90265 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java @@ -0,0 +1,63 @@ +// Copyright 2016 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.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Watcher; + +@JNINamespace("mojo::android") +class WatcherImpl implements Watcher { + private long mImplPtr = nativeCreateWatcher(); + private Callback mCallback; + + @Override + public int start(Handle handle, Core.HandleSignals signals, Callback callback) { + if (mImplPtr == 0) { + return MojoResult.INVALID_ARGUMENT; + } + if (!(handle instanceof HandleBase)) { + return MojoResult.INVALID_ARGUMENT; + } + int result = + nativeStart(mImplPtr, ((HandleBase) handle).getMojoHandle(), signals.getFlags()); + if (result == MojoResult.OK) mCallback = callback; + return result; + } + + @Override + public void cancel() { + if (mImplPtr == 0) { + return; + } + mCallback = null; + nativeCancel(mImplPtr); + } + + @Override + public void destroy() { + if (mImplPtr == 0) { + return; + } + nativeDelete(mImplPtr); + mImplPtr = 0; + } + + @CalledByNative + private void onHandleReady(int result) { + mCallback.onResult(result); + } + + private native long nativeCreateWatcher(); + + private native int nativeStart(long implPtr, int mojoHandle, int flags); + + private native void nativeCancel(long implPtr); + + private native void nativeDelete(long implPtr); +} diff --git a/mojo/android/system/watcher_impl.cc b/mojo/android/system/watcher_impl.cc new file mode 100644 index 0000000000..3344447f41 --- /dev/null +++ b/mojo/android/system/watcher_impl.cc @@ -0,0 +1,109 @@ +// Copyright 2016 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. + +#include "mojo/android/system/watcher_impl.h" + +#include <stddef.h> +#include <stdint.h> + +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +// #include "base/android/jni_registrar.h" +// #include "base/android/library_loader/library_loader_hooks.h" +#include "base/android/scoped_java_ref.h" +#include "base/bind.h" +#include "jni/WatcherImpl_jni.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/simple_watcher.h" + +namespace mojo { +namespace android { + +using base::android::JavaParamRef; + +namespace { + +class WatcherImpl { + public: + WatcherImpl() : watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) {} + + ~WatcherImpl() = default; + + jint Start(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jint mojo_handle, + jint signals) { + java_watcher_.Reset(env, jcaller); + + auto ready_callback = + base::Bind(&WatcherImpl::OnHandleReady, base::Unretained(this)); + + MojoResult result = + watcher_.Watch(mojo::Handle(static_cast<MojoHandle>(mojo_handle)), + static_cast<MojoHandleSignals>(signals), ready_callback); + if (result != MOJO_RESULT_OK) + java_watcher_.Reset(); + + return result; + } + + void Cancel() { + java_watcher_.Reset(); + watcher_.Cancel(); + } + + private: + void OnHandleReady(MojoResult result) { + DCHECK(!java_watcher_.is_null()); + + base::android::ScopedJavaGlobalRef<jobject> java_watcher_preserver; + if (result == MOJO_RESULT_CANCELLED) + java_watcher_preserver = std::move(java_watcher_); + + Java_WatcherImpl_onHandleReady( + base::android::AttachCurrentThread(), + java_watcher_.is_null() ? java_watcher_preserver : java_watcher_, + result); + } + + SimpleWatcher watcher_; + base::android::ScopedJavaGlobalRef<jobject> java_watcher_; + + DISALLOW_COPY_AND_ASSIGN(WatcherImpl); +}; + +} // namespace + +static jlong CreateWatcher(JNIEnv* env, const JavaParamRef<jobject>& jcaller) { + return reinterpret_cast<jlong>(new WatcherImpl); +} + +static jint Start(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jlong watcher_ptr, + jint mojo_handle, + jint signals) { + auto* watcher = reinterpret_cast<WatcherImpl*>(watcher_ptr); + return watcher->Start(env, jcaller, mojo_handle, signals); +} + +static void Cancel(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jlong watcher_ptr) { + reinterpret_cast<WatcherImpl*>(watcher_ptr)->Cancel(); +} + +static void Delete(JNIEnv* env, + const JavaParamRef<jobject>& jcaller, + jlong watcher_ptr) { + delete reinterpret_cast<WatcherImpl*>(watcher_ptr); +} + +bool RegisterWatcherImpl(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/system/watcher_impl.h b/mojo/android/system/watcher_impl.h new file mode 100644 index 0000000000..784f007e25 --- /dev/null +++ b/mojo/android/system/watcher_impl.h @@ -0,0 +1,20 @@ +// Copyright 2016 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. + +#ifndef MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_ +#define MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterWatcherImpl(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_ |