Commit 86936530 authored by mnaganov@chromium.org's avatar mnaganov@chromium.org

[Android] Java Bridge with Gin: implement Java methods invocation

This patch adds GinJavaMethodInvocationHelper class which serves
for coercion of arguments and return values of Java methods to / from
base::Value and GinJavaBridgeValue.

The coercion code is taken from the existing implementation
(JavaBoundObject) with required conversions to use base::Value and
GinJavaBridgeValue instead of NPVARIANT and friends.

There are extensive Java tests for coercion, so here we only add some
unit tests for edge cases.

This patch also adds a trivial EventLog class for writing into Android
Event Log from C++ code.

BUG=355644
R=bulach@chromium.org, torne@chromium.org
TBR=brettw@chromium.org

Review URL: https://codereview.chromium.org/302173006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@276586 0039d316-1c4b-4281-b951-d872f2087c98
parent 62115ba4
......@@ -28,6 +28,8 @@ component("base") {
"android/content_uri_utils.cc",
"android/content_uri_utils.h",
"android/cpu_features.cc",
"android/event_log.cc",
"android/event_log.h",
"android/fifo_utils.cc",
"android/fifo_utils.h",
"android/important_file_writer_android.cc",
......@@ -1317,6 +1319,7 @@ if (is_android) {
"android/java/src/org/chromium/base/CommandLine.java",
"android/java/src/org/chromium/base/ContentUriUtils.java",
"android/java/src/org/chromium/base/CpuFeatures.java",
"android/java/src/org/chromium/base/EventLog.java",
"android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
"android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
"android/java/src/org/chromium/base/MemoryPressureListener.java",
......
......@@ -9,6 +9,7 @@
#include "base/android/command_line_android.h"
#include "base/android/content_uri_utils.h"
#include "base/android/cpu_features.h"
#include "base/android/event_log.h"
#include "base/android/important_file_writer_android.h"
#include "base/android/java_handler_thread.h"
#include "base/android/jni_android.h"
......@@ -34,6 +35,7 @@ static RegistrationMethod kBaseRegisteredMethods[] = {
{ "CommandLine", base::android::RegisterCommandLine },
{ "ContentUriUtils", base::RegisterContentUriUtils },
{ "CpuFeatures", base::android::RegisterCpuFeatures },
{ "EventLog", base::android::RegisterEventLog },
{ "ImportantFileWriterAndroid",
base::android::RegisterImportantFileWriterAndroid },
{ "MemoryPressureListenerAndroid",
......
// Copyright 2013 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/event_log.h"
#include "jni/EventLog_jni.h"
namespace base {
namespace android {
void EventLogWriteInt(int tag, int value) {
Java_EventLog_writeEvent(AttachCurrentThread(), tag, value);
}
bool RegisterEventLog(JNIEnv* env) {
return RegisterNativesImpl(env);
}
} // namespace android
} // namespace base
// 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 BASE_ANDROID_EVENT_LOG_H_
#define BASE_ANDROID_EVENT_LOG_H_
#include <jni.h>
#include "base/base_export.h"
namespace base {
namespace android {
void BASE_EXPORT EventLogWriteInt(int tag, int value);
bool RegisterEventLog(JNIEnv* env);
} // namespace android
} // namespace base
#endif // BASE_ANDROID_EVENT_LOG_H_
// 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.base;
/**
* A simple interface to Android's EventLog to be used by native code.
*/
@JNINamespace("base::android")
public class EventLog {
@CalledByNative
public static void writeEvent(int tag, int value) {
android.util.EventLog.writeEvent(tag, value);
}
}
......@@ -25,6 +25,8 @@ class BASE_EXPORT JavaObjectWeakGlobalRef {
base::android::ScopedJavaLocalRef<jobject> get(JNIEnv* env) const;
bool is_empty() const { return obj_ == NULL; }
void reset();
private:
......
......@@ -1282,6 +1282,7 @@
'android/java/src/org/chromium/base/CommandLine.java',
'android/java/src/org/chromium/base/ContentUriUtils.java',
'android/java/src/org/chromium/base/CpuFeatures.java',
'android/java/src/org/chromium/base/EventLog.java',
'android/java/src/org/chromium/base/ImportantFileWriterAndroid.java',
'android/java/src/org/chromium/base/library_loader/LibraryLoader.java',
'android/java/src/org/chromium/base/MemoryPressureListener.java',
......
......@@ -37,6 +37,8 @@
'android/content_uri_utils.cc',
'android/content_uri_utils.h',
'android/cpu_features.cc',
'android/event_log.cc',
'android/event_log.h',
'android/fifo_utils.cc',
'android/fifo_utils.h',
'android/important_file_writer_android.cc',
......
// 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 CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_BOUND_OBJECT_H_
#define CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_BOUND_OBJECT_H_
#include "base/id_map.h"
#include "base/memory/ref_counted.h"
namespace content {
class GinJavaBoundObject
: public base::RefCountedThreadSafe<GinJavaBoundObject> {
public:
typedef IDMap<scoped_refptr<GinJavaBoundObject>, IDMapOwnPointer> ObjectMap;
typedef ObjectMap::KeyType ObjectID;
private:
friend class base::RefCountedThreadSafe<GinJavaBoundObject>;
GinJavaBoundObject() {}
~GinJavaBoundObject() {}
};
} // namespace content
#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_BOUND_OBJECT_H_
// 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 CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_METHOD_INVOCATION_HELPER_H_
#define CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_METHOD_INVOCATION_HELPER_H_
#include <map>
#include "base/android/jni_weak_ref.h"
#include "base/android/scoped_java_ref.h"
#include "base/memory/ref_counted.h"
#include "base/values.h"
#include "content/browser/renderer_host/java/gin_java_bound_object.h"
#include "content/browser/renderer_host/java/java_type.h"
#include "content/common/content_export.h"
namespace content {
class JavaMethod;
// Instances of this class are created and initialized on the UI thread, do
// their work on the background thread, and then again examined on the UI
// thread. They don't work on both threads simultaneously, though.
class CONTENT_EXPORT GinJavaMethodInvocationHelper
: public base::RefCountedThreadSafe<GinJavaMethodInvocationHelper> {
public:
// DispatcherDelegate is used on the UI thread
class DispatcherDelegate {
public:
DispatcherDelegate() {}
virtual ~DispatcherDelegate() {}
virtual JavaObjectWeakGlobalRef GetObjectWeakRef(
GinJavaBoundObject::ObjectID object_id) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(DispatcherDelegate);
};
// ObjectDelegate is used in the background thread
class ObjectDelegate {
public:
ObjectDelegate() {}
virtual ~ObjectDelegate() {}
virtual base::android::ScopedJavaLocalRef<jobject> GetLocalRef(
JNIEnv* env) = 0;
virtual const JavaMethod* FindMethod(const std::string& method_name,
size_t num_parameters) = 0;
virtual bool IsObjectGetClassMethod(const JavaMethod* method) = 0;
virtual const base::android::JavaRef<jclass>& GetSafeAnnotationClass() = 0;
private:
DISALLOW_COPY_AND_ASSIGN(ObjectDelegate);
};
GinJavaMethodInvocationHelper(scoped_ptr<ObjectDelegate> object,
const std::string& method_name,
const base::ListValue& arguments);
void Init(DispatcherDelegate* dispatcher);
// Called on the background thread
void Invoke();
// Called on the UI thread
bool HoldsPrimitiveResult();
const base::ListValue& GetPrimitiveResult();
const base::android::JavaRef<jobject>& GetObjectResult();
const base::android::JavaRef<jclass>& GetSafeAnnotationClass();
const std::string& GetErrorMessage();
private:
friend class base::RefCountedThreadSafe<GinJavaMethodInvocationHelper>;
~GinJavaMethodInvocationHelper();
// Called on the UI thread
void BuildObjectRefsFromListValue(DispatcherDelegate* dispatcher,
const base::Value* list_value);
void BuildObjectRefsFromDictionaryValue(DispatcherDelegate* dispatcher,
const base::Value* dict_value);
bool AppendObjectRef(DispatcherDelegate* dispatcher,
const base::Value* raw_value);
// Called on the background thread.
void InvokeMethod(jobject object,
const JavaType& return_type,
jmethodID id,
jvalue* parameters);
void SetInvocationFailure(const char* error_message);
void SetPrimitiveResult(const base::ListValue& result_wrapper);
void SetObjectResult(
const base::android::JavaRef<jobject>& object,
const base::android::JavaRef<jclass>& safe_annotation_clazz);
typedef std::map<GinJavaBoundObject::ObjectID,
JavaObjectWeakGlobalRef> ObjectRefs;
scoped_ptr<ObjectDelegate> object_;
const std::string method_name_;
scoped_ptr<base::ListValue> arguments_;
ObjectRefs object_refs_;
bool holds_primitive_result_;
scoped_ptr<base::ListValue> primitive_result_;
std::string error_message_;
base::android::ScopedJavaGlobalRef<jobject> object_result_;
base::android::ScopedJavaGlobalRef<jclass> safe_annotation_clazz_;
DISALLOW_COPY_AND_ASSIGN(GinJavaMethodInvocationHelper);
};
} // namespace content
#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_METHOD_INVOCATION_HELPER_H_
// 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 "content/browser/renderer_host/java/gin_java_method_invocation_helper.h"
#include "base/android/jni_android.h"
#include "content/common/android/gin_java_bridge_value.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
class NullObjectDelegate
: public GinJavaMethodInvocationHelper::ObjectDelegate {
public:
NullObjectDelegate() {}
virtual ~NullObjectDelegate() {}
virtual base::android::ScopedJavaLocalRef<jobject> GetLocalRef(
JNIEnv* env) OVERRIDE {
return base::android::ScopedJavaLocalRef<jobject>();
}
virtual const JavaMethod* FindMethod(const std::string& method_name,
size_t num_parameters) OVERRIDE {
return NULL;
}
virtual bool IsObjectGetClassMethod(const JavaMethod* method) OVERRIDE {
return false;
}
virtual const base::android::JavaRef<jclass>& GetSafeAnnotationClass()
OVERRIDE {
return safe_annotation_class_;
}
private:
base::android::ScopedJavaLocalRef<jclass> safe_annotation_class_;
DISALLOW_COPY_AND_ASSIGN(NullObjectDelegate);
};
class NullDispatcherDelegate
: public GinJavaMethodInvocationHelper::DispatcherDelegate {
public:
NullDispatcherDelegate() {}
virtual ~NullDispatcherDelegate() {}
virtual JavaObjectWeakGlobalRef GetObjectWeakRef(
GinJavaBoundObject::ObjectID object_id) OVERRIDE {
return JavaObjectWeakGlobalRef();
}
DISALLOW_COPY_AND_ASSIGN(NullDispatcherDelegate);
};
} // namespace
class GinJavaMethodInvocationHelperTest : public testing::Test {
};
namespace {
class CountingDispatcherDelegate
: public GinJavaMethodInvocationHelper::DispatcherDelegate {
public:
CountingDispatcherDelegate() {}
virtual ~CountingDispatcherDelegate() {}
virtual JavaObjectWeakGlobalRef GetObjectWeakRef(
GinJavaBoundObject::ObjectID object_id) OVERRIDE {
counters_[object_id]++;
return JavaObjectWeakGlobalRef();
}
void AssertInvocationsCount(GinJavaBoundObject::ObjectID begin_object_id,
GinJavaBoundObject::ObjectID end_object_id) {
EXPECT_EQ(end_object_id - begin_object_id,
static_cast<int>(counters_.size()));
for (GinJavaBoundObject::ObjectID i = begin_object_id;
i < end_object_id; ++i) {
EXPECT_LT(0, counters_[i]) << "ObjectID: " << i;
}
}
private:
typedef std::map<GinJavaBoundObject::ObjectID, int> Counters;
Counters counters_;
DISALLOW_COPY_AND_ASSIGN(CountingDispatcherDelegate);
};
} // namespace
TEST_F(GinJavaMethodInvocationHelperTest, RetrievalOfObjectsNoObjects) {
base::ListValue no_objects;
for (int i = 0; i < 10; ++i) {
no_objects.AppendInteger(i);
}
scoped_refptr<GinJavaMethodInvocationHelper> helper =
new GinJavaMethodInvocationHelper(
scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>(
new NullObjectDelegate()),
"foo",
no_objects);
CountingDispatcherDelegate counter;
helper->Init(&counter);
counter.AssertInvocationsCount(0, 0);
}
TEST_F(GinJavaMethodInvocationHelperTest, RetrievalOfObjectsHaveObjects) {
base::ListValue objects;
objects.AppendInteger(100);
objects.Append(GinJavaBridgeValue::CreateObjectIDValue(1).release());
base::ListValue* sub_list = new base::ListValue();
sub_list->AppendInteger(200);
sub_list->Append(GinJavaBridgeValue::CreateObjectIDValue(2).release());
objects.Append(sub_list);
base::DictionaryValue* sub_dict = new base::DictionaryValue();
sub_dict->SetInteger("1", 300);
sub_dict->Set("2", GinJavaBridgeValue::CreateObjectIDValue(3).release());
objects.Append(sub_dict);
base::ListValue* sub_list_with_dict = new base::ListValue();
base::DictionaryValue* sub_sub_dict = new base::DictionaryValue();
sub_sub_dict->Set("1", GinJavaBridgeValue::CreateObjectIDValue(4).release());
sub_list_with_dict->Append(sub_sub_dict);
objects.Append(sub_list_with_dict);
base::DictionaryValue* sub_dict_with_list = new base::DictionaryValue();
base::ListValue* sub_sub_list = new base::ListValue();
sub_sub_list->Append(GinJavaBridgeValue::CreateObjectIDValue(5).release());
sub_dict_with_list->Set("1", sub_sub_list);
objects.Append(sub_dict_with_list);
scoped_refptr<GinJavaMethodInvocationHelper> helper =
new GinJavaMethodInvocationHelper(
scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>(
new NullObjectDelegate()),
"foo",
objects);
CountingDispatcherDelegate counter;
helper->Init(&counter);
counter.AssertInvocationsCount(1, 6);
}
TEST_F(GinJavaMethodInvocationHelperTest, HandleObjectIsGone) {
base::ListValue no_objects;
scoped_refptr<GinJavaMethodInvocationHelper> helper =
new GinJavaMethodInvocationHelper(
scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>(
new NullObjectDelegate()),
"foo",
no_objects);
NullDispatcherDelegate dispatcher;
helper->Init(&dispatcher);
EXPECT_TRUE(helper->GetErrorMessage().empty());
helper->Invoke();
EXPECT_TRUE(helper->HoldsPrimitiveResult());
EXPECT_TRUE(helper->GetPrimitiveResult().empty());
EXPECT_FALSE(helper->GetErrorMessage().empty());
}
namespace {
class MethodNotFoundObjectDelegate : public NullObjectDelegate {
public:
MethodNotFoundObjectDelegate() : find_method_called_(false) {}
virtual ~MethodNotFoundObjectDelegate() {}
virtual base::android::ScopedJavaLocalRef<jobject> GetLocalRef(
JNIEnv* env) OVERRIDE {
return base::android::ScopedJavaLocalRef<jobject>(
env, static_cast<jobject>(env->FindClass("java/lang/String")));
}
virtual const JavaMethod* FindMethod(const std::string& method_name,
size_t num_parameters) OVERRIDE {
find_method_called_ = true;
return NULL;
}
bool find_method_called() const { return find_method_called_; }
protected:
bool find_method_called_;
private:
DISALLOW_COPY_AND_ASSIGN(MethodNotFoundObjectDelegate);
};
} // namespace
TEST_F(GinJavaMethodInvocationHelperTest, HandleMethodNotFound) {
base::ListValue no_objects;
MethodNotFoundObjectDelegate* object_delegate =
new MethodNotFoundObjectDelegate();
scoped_refptr<GinJavaMethodInvocationHelper> helper =
new GinJavaMethodInvocationHelper(
scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>(
object_delegate),
"foo",
no_objects);
NullDispatcherDelegate dispatcher;
helper->Init(&dispatcher);
EXPECT_FALSE(object_delegate->find_method_called());
EXPECT_TRUE(helper->GetErrorMessage().empty());
helper->Invoke();
EXPECT_TRUE(object_delegate->find_method_called());
EXPECT_TRUE(helper->HoldsPrimitiveResult());
EXPECT_TRUE(helper->GetPrimitiveResult().empty());
EXPECT_FALSE(helper->GetErrorMessage().empty());
}
namespace {
class GetClassObjectDelegate : public MethodNotFoundObjectDelegate {
public:
GetClassObjectDelegate() : get_class_called_(false) {}
virtual ~GetClassObjectDelegate() {}
virtual const JavaMethod* FindMethod(const std::string& method_name,
size_t num_parameters) OVERRIDE {
find_method_called_ = true;
return kFakeGetClass;
}
virtual bool IsObjectGetClassMethod(const JavaMethod* method) OVERRIDE {
get_class_called_ = true;
return kFakeGetClass == method;
}
bool get_class_called() const { return get_class_called_; }
private:
static const JavaMethod* kFakeGetClass;
bool get_class_called_;
DISALLOW_COPY_AND_ASSIGN(GetClassObjectDelegate);
};
// We don't expect GinJavaMethodInvocationHelper to actually invoke the
// method, since the point of the test is to verify whether calls to
// 'getClass' get blocked.
const JavaMethod* GetClassObjectDelegate::kFakeGetClass =
(JavaMethod*)0xdeadbeef;
} // namespace
TEST_F(GinJavaMethodInvocationHelperTest, HandleGetClassInvocation) {
base::ListValue no_objects;
GetClassObjectDelegate* object_delegate =
new GetClassObjectDelegate();
scoped_refptr<GinJavaMethodInvocationHelper> helper =
new GinJavaMethodInvocationHelper(
scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>(
object_delegate),
"foo",
no_objects);
NullDispatcherDelegate dispatcher;
helper->Init(&dispatcher);
EXPECT_FALSE(object_delegate->find_method_called());
EXPECT_FALSE(object_delegate->get_class_called());
EXPECT_TRUE(helper->GetErrorMessage().empty());
helper->Invoke();
EXPECT_TRUE(object_delegate->find_method_called());
EXPECT_TRUE(object_delegate->get_class_called());
EXPECT_TRUE(helper->HoldsPrimitiveResult());
EXPECT_TRUE(helper->GetPrimitiveResult().empty());
EXPECT_FALSE(helper->GetErrorMessage().empty());
}
} // namespace content
// 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 CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_SCRIPT_TO_JAVA_TYPES_COERCION_H_
#define CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_SCRIPT_TO_JAVA_TYPES_COERCION_H_
#include <map>
#include "base/android/jni_weak_ref.h"
#include "base/values.h"
#include "content/browser/renderer_host/java/gin_java_bound_object.h"
#include "content/browser/renderer_host/java/java_type.h"
namespace content {
typedef std::map<GinJavaBoundObject::ObjectID, JavaObjectWeakGlobalRef>
ObjectRefs;
jvalue CoerceJavaScriptValueToJavaValue(
JNIEnv* env,
const base::Value* value,
const JavaType& target_type,
bool coerce_to_string,
const ObjectRefs& object_refs);
void ReleaseJavaValueIfRequired(JNIEnv* env,
jvalue* value,
const JavaType& type);
} // namespace content
#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_SCRIPT_TO_JAVA_TYPES_COERCION_H_
......@@ -964,6 +964,11 @@
'browser/renderer_host/input/web_input_event_util.h',
'browser/renderer_host/input/web_input_event_util_posix.cc',
'browser/renderer_host/input/web_input_event_util_posix.h',
'browser/renderer_host/java/gin_java_bound_object.h',
'browser/renderer_host/java/gin_java_method_invocation_helper.cc',
'browser/renderer_host/java/gin_java_method_invocation_helper.h',
'browser/renderer_host/java/gin_java_script_to_java_types_coercion.cc',
'browser/renderer_host/java/gin_java_script_to_java_types_coercion.h',
'browser/renderer_host/java/java_bound_object.cc',
'browser/renderer_host/java/java_bound_object.h',
'browser/renderer_host/java/java_bridge_channel_host.cc',
......
......@@ -875,6 +875,7 @@
}],
['OS == "android"', {
'sources': [
'browser/renderer_host/java/gin_java_method_invocation_helper_unittest.cc',
'browser/renderer_host/java/jni_helper_unittest.cc',
'renderer/java/gin_java_bridge_value_converter_unittest.cc',
],
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment