diff options
Diffstat (limited to 'chromium/base/android')
79 files changed, 8813 insertions, 0 deletions
diff --git a/chromium/base/android/OWNERS b/chromium/base/android/OWNERS new file mode 100644 index 00000000000..ebc6e2624f0 --- /dev/null +++ b/chromium/base/android/OWNERS @@ -0,0 +1,3 @@ +bulach@chromium.org +joth@chromium.org +yfriedman@chromium.org diff --git a/chromium/base/android/activity_state_list.h b/chromium/base/android/activity_state_list.h new file mode 100644 index 00000000000..43c0f803da3 --- /dev/null +++ b/chromium/base/android/activity_state_list.h @@ -0,0 +1,16 @@ +// Copyright (c) 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. + +// This file intentionally does not have header guards, it's included +// inside a macro to generate enum values. + +#ifndef DEFINE_ACTIVITY_STATE +#error "DEFINE_ACTIVITY_STATE should be defined before including this file" +#endif +DEFINE_ACTIVITY_STATE(CREATED, 1) +DEFINE_ACTIVITY_STATE(STARTED, 2) +DEFINE_ACTIVITY_STATE(RESUMED, 3) +DEFINE_ACTIVITY_STATE(PAUSED, 4) +DEFINE_ACTIVITY_STATE(STOPPED, 5) +DEFINE_ACTIVITY_STATE(DESTROYED, 6) diff --git a/chromium/base/android/activity_status.cc b/chromium/base/android/activity_status.cc new file mode 100644 index 00000000000..4d0be32ef93 --- /dev/null +++ b/chromium/base/android/activity_status.cc @@ -0,0 +1,66 @@ +// 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/activity_status.h" + +#include <jni.h> + +#include "base/memory/singleton.h" +#include "jni/ActivityStatus_jni.h" + +namespace base { +namespace android { + +ActivityStatus::Listener::Listener( + const ActivityStatus::StateChangeCallback& callback) + : callback_(callback) { + ActivityStatus::GetInstance()->RegisterListener(this); +} + +ActivityStatus::Listener::~Listener() { + ActivityStatus::GetInstance()->UnregisterListener(this); +} + +void ActivityStatus::Listener::Notify(ActivityState state) { + callback_.Run(state); +} + +// static +ActivityStatus* ActivityStatus::GetInstance() { + return Singleton<ActivityStatus, + LeakySingletonTraits<ActivityStatus> >::get(); +} + +static void OnActivityStateChange(JNIEnv* env, jclass clazz, int new_state) { + ActivityStatus* activity_status = ActivityStatus::GetInstance(); + ActivityState activity_state = static_cast<ActivityState>(new_state); + activity_status->OnActivityStateChange(activity_state); +} + +bool ActivityStatus::RegisterBindings(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +ActivityStatus::ActivityStatus() + : observers_(new ObserverListThreadSafe<Listener>()) { + Java_ActivityStatus_registerThreadSafeNativeStateListener( + base::android::AttachCurrentThread()); +} + +ActivityStatus::~ActivityStatus() {} + +void ActivityStatus::RegisterListener(Listener* listener) { + observers_->AddObserver(listener); +} + +void ActivityStatus::UnregisterListener(Listener* listener) { + observers_->RemoveObserver(listener); +} + +void ActivityStatus::OnActivityStateChange(ActivityState new_state) { + observers_->Notify(&ActivityStatus::Listener::Notify, new_state); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/activity_status.h b/chromium/base/android/activity_status.h new file mode 100644 index 00000000000..7975a789cd0 --- /dev/null +++ b/chromium/base/android/activity_status.h @@ -0,0 +1,98 @@ +// 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. + +#ifndef BASE_ANDROID_ACTIVITY_STATUS_H_ +#define BASE_ANDROID_ACTIVITY_STATUS_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/singleton.h" +#include "base/observer_list_threadsafe.h" + +namespace base { +namespace android { + +// Define activity state values like ACTIVITY_STATE_CREATED in a +// way that ensures they're always the same than their Java counterpart. +enum ActivityState { +#define DEFINE_ACTIVITY_STATE(x, y) ACTIVITY_STATE_##x = y, +#include "base/android/activity_state_list.h" +#undef DEFINE_ACTIVITY_STATE +}; + +// A native helper class to listen to state changes of the current +// Android Activity. This mirrors org.chromium.base.ActivityStatus. +// any thread. +// +// To start listening, create a new instance, passing a callback to a +// function that takes an ActivityState parameter. To stop listening, +// simply delete the listener object. The implementation guarantees +// that the callback will always be called on the thread that created +// the listener. +// +// Example: +// +// void OnActivityStateChange(ActivityState state) { +// ... +// } +// +// // Start listening. +// ActivityStatus::Listener* my_listener = +// new ActivityStatus::Listener(base::Bind(&OnActivityStateChange)); +// +// ... +// +// // Stop listening. +// delete my_listener +// +class BASE_EXPORT ActivityStatus { + public: + typedef base::Callback<void(ActivityState)> StateChangeCallback; + + class Listener { + public: + explicit Listener(const StateChangeCallback& callback); + ~Listener(); + + private: + friend class ActivityStatus; + + void Notify(ActivityState state); + + StateChangeCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(Listener); + }; + + // NOTE: The Java ActivityStatus is a singleton too. + static ActivityStatus* GetInstance(); + + // Internal use: must be public to be called from base_jni_registrar.cc + static bool RegisterBindings(JNIEnv* env); + + // Internal use only: must be public to be called from JNI and unit tests. + void OnActivityStateChange(ActivityState new_state); + + private: + friend struct DefaultSingletonTraits<ActivityStatus>; + + ActivityStatus(); + ~ActivityStatus(); + + void RegisterListener(Listener* listener); + void UnregisterListener(Listener* listener); + + scoped_refptr<ObserverListThreadSafe<Listener> > observers_; + + DISALLOW_COPY_AND_ASSIGN(ActivityStatus); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_ACTIVITY_STATUS_H_ diff --git a/chromium/base/android/activity_status_unittest.cc b/chromium/base/android/activity_status_unittest.cc new file mode 100644 index 00000000000..3eb0d10f736 --- /dev/null +++ b/chromium/base/android/activity_status_unittest.cc @@ -0,0 +1,128 @@ +// 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/activity_status.h" +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { + +using base::android::ScopedJavaLocalRef; + +// An invalid ActivityState value. +const ActivityState kInvalidActivityState = static_cast<ActivityState>(100); + +// Used to generate a callback that stores the new state at a given location. +void StoreStateTo(ActivityState* target, ActivityState state) { + *target = state; +} + +void RunTasksUntilIdle() { + RunLoop run_loop; + run_loop.RunUntilIdle(); +} + +// Shared state for the multi-threaded test. +// This uses a thread to register for events and listen to them, while state +// changes are forced on the main thread. +class MultiThreadedTest { + public: + MultiThreadedTest() + : activity_status_(ActivityStatus::GetInstance()), + state_(kInvalidActivityState), + event_(false, false), + thread_("ActivityStatusTest thread"), + main_() { + } + + void Run() { + // Start the thread and tell it to register for events. + thread_.Start(); + thread_.message_loop() + ->PostTask(FROM_HERE, + base::Bind(&MultiThreadedTest::RegisterThreadForEvents, + base::Unretained(this))); + + // Wait for its completion. + event_.Wait(); + + // Change state, then wait for the thread to modify state. + activity_status_->OnActivityStateChange(ACTIVITY_STATE_CREATED); + event_.Wait(); + EXPECT_EQ(ACTIVITY_STATE_CREATED, state_); + + // Again + activity_status_->OnActivityStateChange(ACTIVITY_STATE_DESTROYED); + event_.Wait(); + EXPECT_EQ(ACTIVITY_STATE_DESTROYED, state_); + } + + private: + void ExpectOnThread() { + EXPECT_EQ(thread_.message_loop(), base::MessageLoop::current()); + } + + void RegisterThreadForEvents() { + ExpectOnThread(); + listener_.reset(new ActivityStatus::Listener(base::Bind( + &MultiThreadedTest::StoreStateAndSignal, base::Unretained(this)))); + EXPECT_TRUE(listener_.get()); + event_.Signal(); + } + + void StoreStateAndSignal(ActivityState state) { + ExpectOnThread(); + state_ = state; + event_.Signal(); + } + + ActivityStatus* const activity_status_; + ActivityState state_; + base::WaitableEvent event_; + base::Thread thread_; + base::MessageLoop main_; + scoped_ptr<ActivityStatus::Listener> listener_; +}; + +} // namespace + +TEST(ActivityStatusTest, SingleThread) { + MessageLoop message_loop; + + ActivityState result = kInvalidActivityState; + + // Create a new listener that stores the new state into |result| on every + // state change. + ActivityStatus::Listener listener( + base::Bind(&StoreStateTo, base::Unretained(&result))); + + EXPECT_EQ(kInvalidActivityState, result); + + ActivityStatus* const activity_status = ActivityStatus::GetInstance(); + activity_status->OnActivityStateChange(ACTIVITY_STATE_CREATED); + RunTasksUntilIdle(); + EXPECT_EQ(ACTIVITY_STATE_CREATED, result); + + activity_status->OnActivityStateChange(ACTIVITY_STATE_DESTROYED); + RunTasksUntilIdle(); + EXPECT_EQ(ACTIVITY_STATE_DESTROYED, result); +} + +TEST(ActivityStatusTest, TwoThreads) { + MultiThreadedTest test; + test.Run(); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/base_jni_registrar.cc b/chromium/base/android/base_jni_registrar.cc new file mode 100644 index 00000000000..0645c73030c --- /dev/null +++ b/chromium/base/android/base_jni_registrar.cc @@ -0,0 +1,58 @@ +// 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. + +#include "base/android/base_jni_registrar.h" + +#include "base/android/activity_status.h" +#include "base/android/build_info.h" +#include "base/android/cpu_features.h" +#include "base/android/important_file_writer_android.h" +#include "base/android/java_handler_thread.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/memory_pressure_listener_android.h" +#include "base/android/path_service_android.h" +#include "base/android/path_utils.h" +#include "base/android/sys_utils.h" +#include "base/android/thread_utils.h" +#include "base/basictypes.h" +#include "base/debug/trace_event.h" +#include "base/message_loop/message_pump_android.h" +#include "base/power_monitor/power_monitor_device_source_android.h" + +#if defined(GOOGLE_TV) +#include "base/android/context_types.h" +#endif + +namespace base { +namespace android { + +static RegistrationMethod kBaseRegisteredMethods[] = { + { "ActivityStatus", base::android::ActivityStatus::RegisterBindings }, + { "BuildInfo", base::android::BuildInfo::RegisterBindings }, +#if defined(GOOGLE_TV) + { "ContextTypes", base::android::RegisterContextTypes }, +#endif + { "CpuFeatures", base::android::RegisterCpuFeatures }, + { "ImportantFileWriterAndroid", + base::android::RegisterImportantFileWriterAndroid }, + { "MemoryPressureListenerAndroid", + base::android::MemoryPressureListenerAndroid::Register }, + { "JavaHandlerThread", base::android::JavaHandlerThread::RegisterBindings }, + { "PathService", base::android::RegisterPathService }, + { "PathUtils", base::android::RegisterPathUtils }, + { "SystemMessageHandler", base::MessagePumpForUI::RegisterBindings }, + { "SysUtils", base::android::SysUtils::Register }, + { "PowerMonitor", base::RegisterPowerMonitor }, + { "ThreadUtils", base::RegisterThreadUtils }, +}; + +bool RegisterJni(JNIEnv* env) { + TRACE_EVENT0("startup", "base_android::RegisterJni"); + return RegisterNativeMethods(env, kBaseRegisteredMethods, + arraysize(kBaseRegisteredMethods)); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/base_jni_registrar.h b/chromium/base/android/base_jni_registrar.h new file mode 100644 index 00000000000..fdaf5f2c6cd --- /dev/null +++ b/chromium/base/android/base_jni_registrar.h @@ -0,0 +1,21 @@ +// 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. + +#ifndef BASE_ANDROID_BASE_JNI_REGISTRAR_H_ +#define BASE_ANDROID_BASE_JNI_REGISTRAR_H_ + +#include <jni.h> + +#include "base/base_export.h" + +namespace base { +namespace android { + +// Register all JNI bindings necessary for base. +BASE_EXPORT bool RegisterJni(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_BASE_JNI_REGISTRAR_H_ diff --git a/chromium/base/android/build_info.cc b/chromium/base/android/build_info.cc new file mode 100644 index 00000000000..cdde6a90052 --- /dev/null +++ b/chromium/base/android/build_info.cc @@ -0,0 +1,78 @@ +// 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. + +#include "base/android/build_info.h" + +#include <string> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "jni/BuildInfo_jni.h" + +namespace { + +// The caller takes ownership of the returned const char*. +const char* StrDupJString(const base::android::JavaRef<jstring>& java_string) { + std::string str = ConvertJavaStringToUTF8(java_string); + return strdup(str.c_str()); +} + +} // namespace + +namespace base { +namespace android { + +struct BuildInfoSingletonTraits { + static BuildInfo* New() { + return new BuildInfo(AttachCurrentThread()); + } + + static void Delete(BuildInfo* x) { + // We're leaking this type, see kRegisterAtExit. + NOTREACHED(); + } + + static const bool kRegisterAtExit = false; + static const bool kAllowedToAccessOnNonjoinableThread = true; +}; + +BuildInfo::BuildInfo(JNIEnv* env) + : device_(StrDupJString(Java_BuildInfo_getDevice(env))), + model_(StrDupJString(Java_BuildInfo_getDeviceModel(env))), + brand_(StrDupJString(Java_BuildInfo_getBrand(env))), + android_build_id_(StrDupJString(Java_BuildInfo_getAndroidBuildId(env))), + android_build_fp_(StrDupJString( + Java_BuildInfo_getAndroidBuildFingerprint(env))), + package_version_code_(StrDupJString(Java_BuildInfo_getPackageVersionCode( + env, GetApplicationContext()))), + package_version_name_(StrDupJString(Java_BuildInfo_getPackageVersionName( + env, GetApplicationContext()))), + package_label_(StrDupJString(Java_BuildInfo_getPackageLabel( + env, GetApplicationContext()))), + package_name_(StrDupJString(Java_BuildInfo_getPackageName( + env, GetApplicationContext()))), + sdk_int_(Java_BuildInfo_getSdkInt(env)), + java_exception_info_(NULL) { +} + +// static +BuildInfo* BuildInfo::GetInstance() { + return Singleton<BuildInfo, BuildInfoSingletonTraits >::get(); +} + +void BuildInfo::set_java_exception_info(const std::string& info) { + DCHECK(!java_exception_info_) << "info should be set only once."; + java_exception_info_ = strndup(info.c_str(), 1024); +} + +// static +bool BuildInfo::RegisterBindings(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/build_info.h b/chromium/base/android/build_info.h new file mode 100644 index 00000000000..a5f44c2e0ac --- /dev/null +++ b/chromium/base/android/build_info.h @@ -0,0 +1,115 @@ +// 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. + +#ifndef BASE_ANDROID_BUILD_INFO_H_ +#define BASE_ANDROID_BUILD_INFO_H_ + +#include <jni.h> + +#include <string> + +#include "base/base_export.h" +#include "base/memory/singleton.h" + +namespace base { +namespace android { + +// BuildInfo is a singleton class that stores android build and device +// information. It will be called from Android specific code and gets used +// primarily in crash reporting. + +// It is also used to store the last java exception seen during JNI. +// TODO(nileshagrawal): Find a better place to store this info. +class BASE_EXPORT BuildInfo { + public: + + ~BuildInfo() {} + + // Static factory method for getting the singleton BuildInfo instance. + // Note that ownership is not conferred on the caller and the BuildInfo in + // question isn't actually freed until shutdown. This is ok because there + // should only be one instance of BuildInfo ever created. + static BuildInfo* GetInstance(); + + // Const char* is used instead of std::strings because these values must be + // available even if the process is in a crash state. Sadly + // std::string.c_str() doesn't guarantee that memory won't be allocated when + // it is called. + const char* device() const { + return device_; + } + + const char* model() const { + return model_; + } + + const char* brand() const { + return brand_; + } + + const char* android_build_id() const { + return android_build_id_; + } + + const char* android_build_fp() const { + return android_build_fp_; + } + + const char* package_version_code() const { + return package_version_code_; + } + + const char* package_version_name() const { + return package_version_name_; + } + + const char* package_label() const { + return package_label_; + } + + const char* package_name() const { + return package_name_; + } + + int sdk_int() const { + return sdk_int_; + } + + const char* java_exception_info() const { + return java_exception_info_; + } + + void set_java_exception_info(const std::string& info); + + static bool RegisterBindings(JNIEnv* env); + + private: + friend struct BuildInfoSingletonTraits; + + explicit BuildInfo(JNIEnv* env); + + // Const char* is used instead of std::strings because these values must be + // available even if the process is in a crash state. Sadly + // std::string.c_str() doesn't guarantee that memory won't be allocated when + // it is called. + const char* const device_; + const char* const model_; + const char* const brand_; + const char* const android_build_id_; + const char* const android_build_fp_; + const char* const package_version_code_; + const char* const package_version_name_; + const char* const package_label_; + const char* const package_name_; + const int sdk_int_; + // This is set via set_java_exception_info, not at constructor time. + const char* java_exception_info_; + + DISALLOW_COPY_AND_ASSIGN(BuildInfo); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_BUILD_INFO_H_ diff --git a/chromium/base/android/context_types.cc b/chromium/base/android/context_types.cc new file mode 100644 index 00000000000..084b1365ce2 --- /dev/null +++ b/chromium/base/android/context_types.cc @@ -0,0 +1,26 @@ +// Copyright (c) 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/context_types.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/files/file_path.h" +#include "jni/ContextTypes_jni.h" + +namespace base { +namespace android { + +bool IsRunningInWebapp() { + JNIEnv* env = AttachCurrentThread(); + return static_cast<bool>( + Java_ContextTypes_isRunningInWebapp(env, GetApplicationContext())); +} + +bool RegisterContextTypes(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/context_types.h b/chromium/base/android/context_types.h new file mode 100644 index 00000000000..a132167199e --- /dev/null +++ b/chromium/base/android/context_types.h @@ -0,0 +1,22 @@ +// Copyright (c) 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. + +#ifndef BASE_ANDROID_CONTEXT_TYPES_H_ +#define BASE_ANDROID_CONTEXT_TYPES_H_ + +#include <jni.h> + +#include "base/base_export.h" + +namespace base { +namespace android { + +BASE_EXPORT bool IsRunningInWebapp(); + +bool RegisterContextTypes(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_CONTEXT_TYPES_H_ diff --git a/chromium/base/android/cpu_features.cc b/chromium/base/android/cpu_features.cc new file mode 100644 index 00000000000..6a1869534b0 --- /dev/null +++ b/chromium/base/android/cpu_features.cc @@ -0,0 +1,26 @@ +// 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. + +#include <cpu-features.h> + +#include "base/android/jni_android.h" +#include "jni/CpuFeatures_jni.h" + +namespace base { +namespace android { + +jint GetCoreCount(JNIEnv*, jclass) { + return android_getCpuCount(); +} + +jlong GetCpuFeatures(JNIEnv*, jclass) { + return android_getCpuFeatures(); +} + +bool RegisterCpuFeatures(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/cpu_features.h b/chromium/base/android/cpu_features.h new file mode 100644 index 00000000000..0a278228d2f --- /dev/null +++ b/chromium/base/android/cpu_features.h @@ -0,0 +1,18 @@ +// 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. + +#ifndef BASE_ANDROID_CPU_FEATURES_H_ +#define BASE_ANDROID_CPU_FEATURES_H_ + +#include <jni.h> + +namespace base { +namespace android { + +bool RegisterCpuFeatures(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_CPU_FEATURES_H_ diff --git a/chromium/base/android/fifo_utils.cc b/chromium/base/android/fifo_utils.cc new file mode 100644 index 00000000000..8f3e95f92db --- /dev/null +++ b/chromium/base/android/fifo_utils.cc @@ -0,0 +1,25 @@ +// 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/fifo_utils.h" + +#include <sys/stat.h> + +#include "base/files/file_path.h" + +namespace base { +namespace android { + +bool CreateFIFO(const FilePath& path, int mode) { + // Default permissions for mkfifo() is ignored, chmod() is required. + return mkfifo(path.value().c_str(), mode) == 0 && + chmod(path.value().c_str(), mode) == 0; +} + +bool RedirectStream(FILE* stream, const FilePath& path, const char* mode) { + return freopen(path.value().c_str(), mode, stream) != NULL; +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/fifo_utils.h b/chromium/base/android/fifo_utils.h new file mode 100644 index 00000000000..1936defaf6e --- /dev/null +++ b/chromium/base/android/fifo_utils.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef BASE_ANDROID_FIFO_UTILS_H_ +#define BASE_ANDROID_FIFO_UTILS_H_ + +#include <stdio.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { + +class FilePath; + +namespace android { + +// Creates a fifo at the given |path| with POSIX permissions set to |mode|, +// returning true if it was successfully created and permissions were set. +BASE_EXPORT bool CreateFIFO(const FilePath& path, int mode); + +// Redirects the |stream| to the file provided by |path| with |mode| +// permissions, returning true if successful. +BASE_EXPORT bool RedirectStream(FILE* stream, + const FilePath& path, + const char* mode); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_FIFO_UTILS_H_ diff --git a/chromium/base/android/important_file_writer_android.cc b/chromium/base/android/important_file_writer_android.cc new file mode 100644 index 00000000000..bcbd785da04 --- /dev/null +++ b/chromium/base/android/important_file_writer_android.cc @@ -0,0 +1,42 @@ +// 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/important_file_writer_android.h" + +#include <string> + +#include "base/android/jni_string.h" +#include "base/files/important_file_writer.h" +#include "base/threading/thread_restrictions.h" +#include "jni/ImportantFileWriterAndroid_jni.h" + +namespace base { +namespace android { + +static jboolean WriteFileAtomically(JNIEnv* env, + jclass /* clazz */, + jstring file_name, + jbyteArray data) { + // This is called on the UI thread during shutdown to save tab data, so + // needs to enable IO. + base::ThreadRestrictions::ScopedAllowIO allow_io; + std::string native_file_name; + base::android::ConvertJavaStringToUTF8(env, file_name, &native_file_name); + base::FilePath path(native_file_name); + int data_length = env->GetArrayLength(data); + jbyte* native_data = env->GetByteArrayElements(data, NULL); + std::string native_data_string(reinterpret_cast<char *>(native_data), + data_length); + bool result = base::ImportantFileWriter::WriteFileAtomically( + path, native_data_string); + env->ReleaseByteArrayElements(data, native_data, JNI_ABORT); + return result; +} + +bool RegisterImportantFileWriterAndroid(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/important_file_writer_android.h b/chromium/base/android/important_file_writer_android.h new file mode 100644 index 00000000000..20956babcea --- /dev/null +++ b/chromium/base/android/important_file_writer_android.h @@ -0,0 +1,18 @@ +// 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. + +#ifndef NATIVE_FRAMEWORK_CHROME_IMPORTANT_FILE_WRITE_ANDROID_H_ +#define NATIVE_FRAMEWORK_CHROME_IMPORTANT_FILE_WRITE_ANDROID_H_ + +#include <jni.h> + +namespace base { +namespace android { + +bool RegisterImportantFileWriterAndroid(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // NATIVE_FRAMEWORK_CHROME_IMPORTANT_FILE_WRITE_ANDROID_H_ diff --git a/chromium/base/android/java/src/org/chromium/base/AccessedByNative.java b/chromium/base/android/java/src/org/chromium/base/AccessedByNative.java new file mode 100644 index 00000000000..0a732587210 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/AccessedByNative.java @@ -0,0 +1,20 @@ +// 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 org.chromium.base; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @AccessedByNative is used to ensure proguard will keep this field, since it's + * only accessed by native. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.CLASS) +public @interface AccessedByNative { + public String value() default ""; +} diff --git a/chromium/base/android/java/src/org/chromium/base/ActivityState.template b/chromium/base/android/java/src/org/chromium/base/ActivityState.template new file mode 100644 index 00000000000..adf990a576d --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/ActivityState.template @@ -0,0 +1,14 @@ +// Copyright (c) 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. + +package org.chromium.base; + +// A simple auto-generated interface used to list the various +// states of an activity as used by both org.chromium.base.ActivityStatus +// and base/android/activity_status.h +interface ActivityState { +#define DEFINE_ACTIVITY_STATE(x,y) public final int x = y; +#include "base/android/activity_state_list.h" +#undef DEFINE_ACTIVITY_STATE +} diff --git a/chromium/base/android/java/src/org/chromium/base/ActivityStatus.java b/chromium/base/android/java/src/org/chromium/base/ActivityStatus.java new file mode 100644 index 00000000000..47472342b20 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/ActivityStatus.java @@ -0,0 +1,136 @@ +// 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 org.chromium.base; + +import android.app.Activity; +import android.os.Handler; +import android.os.Looper; + +/** + * Provides information about the current activity's status, and a way + * to register / unregister listeners for state changes. + */ +@JNINamespace("base::android") +public class ActivityStatus { + + // Constants matching activity states reported to StateListener.onStateChange + // As an implementation detail, these are now defined in the auto-generated + // ActivityState interface, to be shared with C++. + public static final int CREATED = ActivityState.CREATED; + public static final int STARTED = ActivityState.STARTED; + public static final int RESUMED = ActivityState.RESUMED; + public static final int PAUSED = ActivityState.PAUSED; + public static final int STOPPED = ActivityState.STOPPED; + public static final int DESTROYED = ActivityState.DESTROYED; + + // Current main activity, or null if none. + private static Activity sActivity; + + // Current main activity's state. This can be set even if sActivity is null, to simplify unit + // testing. + private static int sActivityState; + + private static final ObserverList<StateListener> sStateListeners = + new ObserverList<StateListener>(); + + /** + * Interface to be implemented by listeners. + */ + public interface StateListener { + /** + * Called when the activity's state changes. + * @param newState New activity state. + */ + public void onActivityStateChange(int newState); + } + + private ActivityStatus() {} + + /** + * Must be called by the main activity when it changes state. + * @param activity Current activity. + * @param newState New state value. + */ + public static void onStateChange(Activity activity, int newState) { + if (sActivity != activity) { + // ActivityStatus is notified with the CREATED event very late during the main activity + // creation to avoid making startup performance worse than it is by notifying observers + // that could do some expensive work. This can lead to non-CREATED events being fired + // before the CREATED event which is problematic. + // TODO(pliard): fix http://crbug.com/176837. + sActivity = activity; + } + sActivityState = newState; + for (StateListener listener : sStateListeners) { + listener.onActivityStateChange(newState); + } + if (newState == DESTROYED) { + sActivity = null; + } + } + + /** + * Indicates that the parent activity is currently paused. + */ + public static boolean isPaused() { + return sActivityState == PAUSED; + } + + /** + * Returns the current main application activity. + */ + public static Activity getActivity() { + return sActivity; + } + + /** + * Returns the current main application activity's state. + */ + public static int getState() { + return sActivityState; + } + + /** + * Registers the given listener to receive activity state changes. + * @param listener Listener to receive state changes. + */ + public static void registerStateListener(StateListener listener) { + sStateListeners.addObserver(listener); + } + + /** + * Unregisters the given listener from receiving activity state changes. + * @param listener Listener that doesn't want to receive state changes. + */ + public static void unregisterStateListener(StateListener listener) { + sStateListeners.removeObserver(listener); + } + + /** + * Registers the single thread-safe native activity status listener. + * This handles the case where the caller is not on the main thread. + * Note that this is used by a leaky singleton object from the native + * side, hence lifecycle management is greatly simplified. + */ + @CalledByNative + private static void registerThreadSafeNativeStateListener() { + ThreadUtils.runOnUiThread(new Runnable () { + @Override + public void run() { + // Register a new listener that calls nativeOnActivityStateChange. + sStateListeners.addObserver(new StateListener() { + @Override + public void onActivityStateChange(int newState) { + nativeOnActivityStateChange(newState); + } + }); + } + }); + } + + // Called to notify the native side of state changes. + // IMPORTANT: This is always called on the main thread! + private static native void nativeOnActivityStateChange(int newState); +} diff --git a/chromium/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java b/chromium/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java new file mode 100644 index 00000000000..8c4ac0fe01b --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java @@ -0,0 +1,140 @@ +// 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. + +package org.chromium.base; + +import android.app.Notification; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewTreeObserver; + +/** + * Utility class to use new APIs that were added after ICS (API level 14). + */ +public class ApiCompatibilityUtils { + + private ApiCompatibilityUtils() { + } + + /** + * Returns true if view's layout direction is right-to-left. + * + * @param view the View whose layout is being considered + */ + public static boolean isLayoutRtl(View view) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } else { + // All layouts are LTR before JB MR1. + return false; + } + } + + /** + * @see android.view.View#setLayoutDirection(int) + */ + public static void setLayoutDirection(View view, int layoutDirection) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + view.setLayoutDirection(layoutDirection); + } else { + // Do nothing. RTL layouts aren't supported before JB MR1. + } + } + + /** + * @see android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int) + */ + public static void setMarginEnd(MarginLayoutParams layoutParams, int end) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + layoutParams.setMarginEnd(end); + } else { + layoutParams.rightMargin = end; + } + } + + /** + * @see android.view.ViewGroup.MarginLayoutParams#getMarginEnd() + */ + public static int getMarginEnd(MarginLayoutParams layoutParams) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return layoutParams.getMarginEnd(); + } else { + return layoutParams.rightMargin; + } + } + + /** + * @see android.view.ViewGroup.MarginLayoutParams#setMarginStart(int) + */ + public static void setMarginStart(MarginLayoutParams layoutParams, int start) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + layoutParams.setMarginStart(start); + } else { + layoutParams.leftMargin = start; + } + } + + /** + * @see android.view.ViewGroup.MarginLayoutParams#getMarginStart() + */ + public static int getMarginStart(MarginLayoutParams layoutParams) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return layoutParams.getMarginStart(); + } else { + return layoutParams.leftMargin; + } + } + + /** + * @see android.view.View#postInvalidateOnAnimation() + */ + public static void postInvalidateOnAnimation(View view) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.postInvalidateOnAnimation(); + } else { + view.postInvalidate(); + } + } + + // These methods have a new name, and the old name is deprecated. + + /** + * @see android.view.View#setBackground(Drawable) + */ + @SuppressWarnings("deprecation") + public static void setBackgroundForView(View view, Drawable drawable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.setBackground(drawable); + } else { + view.setBackgroundDrawable(drawable); + } + } + + /** + * @see android.view.ViewTreeObserver#removeOnGlobalLayoutListener() + */ + @SuppressWarnings("deprecation") + public static void removeOnGlobalLayoutListener( + View view, ViewTreeObserver.OnGlobalLayoutListener listener) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.getViewTreeObserver().removeOnGlobalLayoutListener(listener); + } else { + view.getViewTreeObserver().removeGlobalOnLayoutListener(listener); + } + } + + /** + * @see android.app.Notification.Builder#build() + */ + @SuppressWarnings("deprecation") + public static Notification buildNotification(Notification.Builder builder) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return builder.build(); + } else { + return builder.getNotification(); + } + } +} diff --git a/chromium/base/android/java/src/org/chromium/base/BuildInfo.java b/chromium/base/android/java/src/org/chromium/base/BuildInfo.java new file mode 100644 index 00000000000..81a8f345590 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/BuildInfo.java @@ -0,0 +1,117 @@ +// 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 org.chromium.base; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; +import android.util.Log; + +import org.chromium.base.CalledByNative; + +/** + * BuildInfo is a utility class providing easy access to {@link PackageInfo} + * information. This is primarly of use for accessesing package information + * from native code. + */ +public class BuildInfo { + private static final String TAG = "BuildInfo"; + private static final int MAX_FINGERPRINT_LENGTH = 128; + + /** + * BuildInfo is a static utility class and therefore shouldn't be + * instantiated. + */ + private BuildInfo() { + } + + @CalledByNative + public static String getDevice() { + return Build.DEVICE; + } + + @CalledByNative + public static String getBrand() { + return Build.BRAND; + } + + @CalledByNative + public static String getAndroidBuildId() { + return Build.ID; + } + + /** + * @return The build fingerprint for the current Android install. The value is truncated to a + * 128 characters as this is used for crash and UMA reporting, which should avoid huge + * strings. + */ + @CalledByNative + public static String getAndroidBuildFingerprint() { + return Build.FINGERPRINT.substring( + 0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH)); + } + + @CalledByNative + public static String getDeviceModel() { + return Build.MODEL; + } + + @CalledByNative + public static String getPackageVersionCode(Context context) { + String msg = "versionCode not available."; + try { + PackageManager pm = context.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); + msg = ""; + if (pi.versionCode > 0) { + msg = Integer.toString(pi.versionCode); + } + } catch (NameNotFoundException e) { + Log.d(TAG, msg); + } + return msg; + + } + + @CalledByNative + public static String getPackageVersionName(Context context) { + String msg = "versionName not available"; + try { + PackageManager pm = context.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); + msg = pi.versionName; + } catch (NameNotFoundException e) { + Log.d(TAG, msg); + } + return msg; + } + + @CalledByNative + public static String getPackageLabel(Context context) { + try { + PackageManager packageManager = context.getPackageManager(); + ApplicationInfo appInfo = packageManager.getApplicationInfo(context.getPackageName(), + PackageManager.GET_META_DATA); + CharSequence label = packageManager.getApplicationLabel(appInfo); + return label != null ? label.toString() : ""; + } catch (NameNotFoundException e) { + return ""; + } + } + + @CalledByNative + public static String getPackageName(Context context) { + String packageName = context != null ? context.getPackageName() : null; + return packageName != null ? packageName : ""; + } + + @CalledByNative + public static int getSdkInt() { + return Build.VERSION.SDK_INT; + } +} diff --git a/chromium/base/android/java/src/org/chromium/base/CalledByNative.java b/chromium/base/android/java/src/org/chromium/base/CalledByNative.java new file mode 100644 index 00000000000..db01b0d0720 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/CalledByNative.java @@ -0,0 +1,23 @@ +// 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 org.chromium.base; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNative is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface CalledByNative { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +} diff --git a/chromium/base/android/java/src/org/chromium/base/CalledByNativeUnchecked.java b/chromium/base/android/java/src/org/chromium/base/CalledByNativeUnchecked.java new file mode 100644 index 00000000000..38bb0c04508 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/CalledByNativeUnchecked.java @@ -0,0 +1,27 @@ +// 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 org.chromium.base; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNativeUnchecked is used to generate JNI bindings that do not check for exceptions. + * It only makes sense to use this annotation on methods that declare a throws... spec. + * However, note that the exception received native side maybe an 'unchecked' (RuntimeExpception) + * such as NullPointerException, so the native code should differentiate these cases. + * Usage of this should be very rare; where possible handle exceptions in the Java side and use a + * return value to indicate success / failure. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface CalledByNativeUnchecked { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +} diff --git a/chromium/base/android/java/src/org/chromium/base/ChromiumActivity.java b/chromium/base/android/java/src/org/chromium/base/ChromiumActivity.java new file mode 100644 index 00000000000..65f5ce929b0 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/ChromiumActivity.java @@ -0,0 +1,49 @@ +// 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 org.chromium.base; + +import android.app.Activity; +import android.os.Bundle; + +// All Chromium main activities should extend this class. This allows various sub-systems to +// properly react to activity state changes. +public class ChromiumActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstance) { + super.onCreate(savedInstance); + ActivityStatus.onStateChange(this, ActivityStatus.CREATED); + } + + @Override + protected void onStart() { + super.onStart(); + ActivityStatus.onStateChange(this, ActivityStatus.STARTED); + } + + @Override + protected void onResume() { + super.onResume(); + ActivityStatus.onStateChange(this, ActivityStatus.RESUMED); + } + + @Override + protected void onPause() { + ActivityStatus.onStateChange(this, ActivityStatus.PAUSED); + super.onPause(); + } + + @Override + protected void onStop() { + ActivityStatus.onStateChange(this, ActivityStatus.STOPPED); + super.onStop(); + } + + @Override + protected void onDestroy() { + ActivityStatus.onStateChange(this, ActivityStatus.DESTROYED); + super.onDestroy(); + } +} diff --git a/chromium/base/android/java/src/org/chromium/base/ContextTypes.java b/chromium/base/android/java/src/org/chromium/base/ContextTypes.java new file mode 100644 index 00000000000..35ecd8fd822 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/ContextTypes.java @@ -0,0 +1,96 @@ +// Copyright (c) 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. + +package org.chromium.base; + +import android.content.Context; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; + +/** + * Maintains the {@link Context}-to-"context type" mapping. The context type + * {@code MODE_APP} is chosen for the application context associated with + * the activity running in application mode, while {@code MODE_NORMAL} for main + * Chromium activity. + * + * <p>Used as singleton instance. + */ +public class ContextTypes { + + // Available context types. + public static final int CONTEXT_TYPE_NORMAL = 1; + public static final int CONTEXT_TYPE_WEBAPP = 2; + + private final Map<Context, Integer> mContextMap; + + private ContextTypes() { + mContextMap = new ConcurrentHashMap<Context, Integer>(); + } + + private static class ContextTypesHolder { + private static final ContextTypes INSTANCE = new ContextTypes(); + } + + public static ContextTypes getInstance() { + return ContextTypesHolder.INSTANCE; + } + + /** + * Adds the mapping for the given {@link Context}. + * + * @param context {@link Context} in interest + * @param type the type associated with the context + * @throws IllegalArgumentException if type is not a valid one. + */ + public void put(Context context, int type) throws IllegalArgumentException { + if (type != CONTEXT_TYPE_NORMAL && type != CONTEXT_TYPE_WEBAPP) { + throw new IllegalArgumentException("Wrong context type"); + } + mContextMap.put(context, type); + } + + /** + * Removes the mapping for the given context. + * + * @param context {@link Context} in interest + */ + public void remove(Context context) { + mContextMap.remove(context); + } + + /** + * Returns type of the given context. + * + * @param context {@link Context} in interest + * @return type associated with the context. Returns {@code MODE_NORMAL} by + * default if the mapping for the queried context is not present. + */ + public int getType(Context context) { + Integer contextType = mContextMap.get(context); + return contextType == null ? CONTEXT_TYPE_NORMAL : contextType; + } + + /** + * Returns whether activity is running in web app mode. + * + * @param appContext {@link Context} in interest + * @return {@code true} when activity is running in web app mode. + */ + @CalledByNative + public static boolean isRunningInWebapp(Context appContext) { + return ContextTypes.getInstance().getType(appContext) + == CONTEXT_TYPE_WEBAPP; + } + + /** + * Checks if the mapping exists for the given context. + * + * @param context {@link Context} in interest + * @return {@code true} if the mapping exists; otherwise {@code false} + */ + public boolean contains(Context context) { + return mContextMap.containsKey(context); + } +} diff --git a/chromium/base/android/java/src/org/chromium/base/CpuFeatures.java b/chromium/base/android/java/src/org/chromium/base/CpuFeatures.java new file mode 100644 index 00000000000..f298fb1e165 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/CpuFeatures.java @@ -0,0 +1,40 @@ +// 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 org.chromium.base; + +// The only purpose of this class is to allow sending CPU properties +// from the browser process to sandboxed renderer processes. This is +// needed because sandboxed processes cannot, on ARM, query the kernel +// about the CPU's properties by parsing /proc, so this operation must +// be performed in the browser process, and the result passed to +// renderer ones. +// +// For more context, see http://crbug.com/164154 +// +// Technically, this is a wrapper around the native NDK cpufeatures +// library. The exact CPU features bits are never used in Java so +// there is no point in duplicating their definitions here. +// +@JNINamespace("base::android") +public abstract class CpuFeatures { + /** + * Return the number of CPU Cores on the device. + */ + public static int getCount() { + return nativeGetCoreCount(); + } + + /** + * Return the CPU feature mask. + * This is a 64-bit integer that corresponds to the CPU's features. + * The value comes directly from android_getCpuFeatures(). + */ + public static long getMask() { + return nativeGetCpuFeatures(); + } + + private static native int nativeGetCoreCount(); + private static native long nativeGetCpuFeatures(); +} diff --git a/chromium/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java b/chromium/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java new file mode 100644 index 00000000000..1c7c0185dae --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java @@ -0,0 +1,29 @@ +// Copyright (c) 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. + +package org.chromium.base; + +/** + * This class provides an interface to the native class for writing + * important data files without risking data loss. + */ +@JNINamespace("base::android") +public class ImportantFileWriterAndroid { + + /** + * Write a binary file atomically. + * + * This either writes all the data or leaves the file unchanged. + * + * @param fileName The complete path of the file to be written + * @param data The data to be written to the file + * @return true if the data was written to the file, false if not. + */ + public static boolean writeFileAtomically(String fileName, byte[] data) { + return nativeWriteFileAtomically(fileName, data); + } + + private static native boolean nativeWriteFileAtomically( + String fileName, byte[] data); +} diff --git a/chromium/base/android/java/src/org/chromium/base/JNINamespace.java b/chromium/base/android/java/src/org/chromium/base/JNINamespace.java new file mode 100644 index 00000000000..cfffc91a346 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/JNINamespace.java @@ -0,0 +1,20 @@ +// 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 org.chromium.base; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @JNINamespace is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code using the specified namespace. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface JNINamespace { + public String value(); +} diff --git a/chromium/base/android/java/src/org/chromium/base/JavaHandlerThread.java b/chromium/base/android/java/src/org/chromium/base/JavaHandlerThread.java new file mode 100644 index 00000000000..5f9960e53ce --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/JavaHandlerThread.java @@ -0,0 +1,41 @@ +// 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. + +package org.chromium.base; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; + +/** + * This class is an internal detail of the native counterpart. + * It is instantiated and owned by the native object. + */ +@JNINamespace("base::android") +class JavaHandlerThread { + final HandlerThread mThread; + + private JavaHandlerThread(String name) { + mThread = new HandlerThread(name); + } + + @CalledByNative + private static JavaHandlerThread create(String name) { + return new JavaHandlerThread(name); + } + + @CalledByNative + private void start(final int nativeThread, final int nativeEvent) { + mThread.start(); + new Handler(mThread.getLooper()).post(new Runnable() { + @Override + public void run() { + nativeInitializeThread(nativeThread, nativeEvent); + } + }); + } + + private native void nativeInitializeThread(int nativeJavaHandlerThread, int nativeEvent); +}
\ No newline at end of file diff --git a/chromium/base/android/java/src/org/chromium/base/MemoryPressureLevelList.template b/chromium/base/android/java/src/org/chromium/base/MemoryPressureLevelList.template new file mode 100644 index 00000000000..cebca842e33 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/MemoryPressureLevelList.template @@ -0,0 +1,12 @@ +// 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. + +package org.chromium.base; + +class MemoryPressureLevelList { +#define DEFINE_MEMORY_PRESSURE_LEVEL(name, value) \ + static final int name = value; +#include "base/memory/memory_pressure_level_list.h" +#undef DEFINE_MEMORY_PRESSURE_LEVEL +} diff --git a/chromium/base/android/java/src/org/chromium/base/MemoryPressureListener.java b/chromium/base/android/java/src/org/chromium/base/MemoryPressureListener.java new file mode 100644 index 00000000000..311b0f6ff49 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/MemoryPressureListener.java @@ -0,0 +1,55 @@ +// 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. + +package org.chromium.base; + +import android.content.ComponentCallbacks2; +import android.content.Context; +import android.content.res.Configuration; + + +/** + * This is an internal implementation of the C++ counterpart. + * It registers a ComponentCallbacks2 with the system, and dispatches into + * native. + */ +public class MemoryPressureListener { + @CalledByNative + private static void registerSystemCallback(Context context) { + context.registerComponentCallbacks( + new ComponentCallbacks2() { + @Override + public void onTrimMemory(int level) { + maybeNotifyMemoryPresure(level); + } + + @Override + public void onLowMemory() { + nativeOnMemoryPressure(MemoryPressureLevelList.MEMORY_PRESSURE_CRITICAL); + } + + @Override + public void onConfigurationChanged(Configuration configuration) { + } + }); + } + + /** + * Used by applications to simulate a memory pressure signal. + */ + public static void simulateMemoryPressureSignal(int level) { + maybeNotifyMemoryPresure(level); + } + + private static void maybeNotifyMemoryPresure(int level) { + if (level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { + nativeOnMemoryPressure(MemoryPressureLevelList.MEMORY_PRESSURE_CRITICAL); + } else if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND || + level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { + nativeOnMemoryPressure(MemoryPressureLevelList.MEMORY_PRESSURE_MODERATE); + } + } + + private static native void nativeOnMemoryPressure(int memoryPressureType); +} diff --git a/chromium/base/android/java/src/org/chromium/base/NativeClassQualifiedName.java b/chromium/base/android/java/src/org/chromium/base/NativeClassQualifiedName.java new file mode 100644 index 00000000000..309169bb1d6 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/NativeClassQualifiedName.java @@ -0,0 +1,25 @@ +// 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 org.chromium.base; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @NativeClassQualifiedName is used by the JNI generator to create the necessary JNI + * bindings to call into the specified native class name. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface NativeClassQualifiedName { + /* + * Tells which native class the method is going to be bound to. + * The first parameter of the annotated method must be an int nativePtr pointing to + * an instance of this class. + */ + public String value(); +} diff --git a/chromium/base/android/java/src/org/chromium/base/ObserverList.java b/chromium/base/android/java/src/org/chromium/base/ObserverList.java new file mode 100644 index 00000000000..13a81c5c195 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/ObserverList.java @@ -0,0 +1,177 @@ +// 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. + +package org.chromium.base; + +import java.lang.Iterable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import javax.annotation.concurrent.NotThreadSafe; + +/** + * A container for a list of observers. + * <p/> + * This container can be modified during iteration without invalidating the iterator. + * So, it safely handles the case of an observer removing itself or other observers from the list + * while observers are being notified. + * <p/> + * The implementation (and the interface) is heavily influenced by the C++ ObserverList. + * Notable differences: + * - The iterator implements NOTIFY_EXISTING_ONLY. + * - The FOR_EACH_OBSERVER closure is left to the clients to implement in terms of iterator(). + * <p/> + * This class is not threadsafe. Observers MUST be added, removed and will be notified on the same + * thread this is created. + */ +@NotThreadSafe +public class ObserverList<E> implements Iterable<E> { + public final List<E> mObservers = new ArrayList<E>(); + private int mIterationDepth = 0; + + public ObserverList() {} + + /** + * Add an observer to the list. + * <p/> + * An observer should not be added to the same list more than once. If an iteration is already + * in progress, this observer will be not be visible during that iteration. + */ + public void addObserver(E obs) { + // Avoid adding null elements to the list as they may be removed on a compaction. + if (obs == null || mObservers.contains(obs)) { + assert false; + return; + } + + // Structurally modifying the underlying list here. This means we + // cannot use the underlying list's iterator to iterate over the list. + mObservers.add(obs); + } + + /** + * Remove an observer from the list if it is in the list. + */ + public void removeObserver(E obs) { + int index = mObservers.indexOf(obs); + + if (index == -1) + return; + + if (mIterationDepth == 0) { + // No one is iterating over the list. + mObservers.remove(obs); + } else { + mObservers.set(index, null); + } + } + + public boolean hasObserver(E obs) { + return mObservers.contains(obs); + } + + public void clear() { + if (mIterationDepth == 0) { + mObservers.clear(); + return; + } + + int size = mObservers.size(); + for (int i = 0; i < size; i++) + mObservers.set(i, null); + } + + @Override + public Iterator<E> iterator() { + return new ObserverListIterator(); + } + + /** + * Compact the underlying list be removing null elements. + * <p/> + * Should only be called when mIterationDepth is zero. + */ + private void compact() { + assert mIterationDepth == 0; + // Safe to use the underlying list's iterator, as we know that no-one else + // is iterating over the list. + Iterator<E> it = mObservers.iterator(); + while (it.hasNext()) { + E el = it.next(); + if (el == null) + it.remove(); + } + } + + private void incrementIterationDepth() { + mIterationDepth++; + } + + private void decrementIterationDepthAndCompactIfNeeded() { + mIterationDepth--; + assert mIterationDepth >= 0; + if (mIterationDepth == 0) + compact(); + } + + private int getSize() { + return mObservers.size(); + } + + private E getObserverAt(int index) { + return mObservers.get(index); + } + + private class ObserverListIterator implements Iterator<E> { + private final int mListEndMarker; + private int mIndex = 0; + private boolean mIsExhausted = false; + + private ObserverListIterator() { + ObserverList.this.incrementIterationDepth(); + mListEndMarker = ObserverList.this.getSize(); + } + + @Override + public boolean hasNext() { + int lookupIndex = mIndex; + while (lookupIndex < mListEndMarker && + ObserverList.this.getObserverAt(lookupIndex) == null) + lookupIndex++; + if (lookupIndex < mListEndMarker) + return true; + + // We have reached the end of the list, allow for compaction. + compactListIfNeeded(); + return false; + } + + @Override + public E next() { + // Advance if the current element is null. + while (mIndex < mListEndMarker && ObserverList.this.getObserverAt(mIndex) == null) + mIndex++; + if (mIndex < mListEndMarker) + return ObserverList.this.getObserverAt(mIndex++); + + // We have reached the end of the list, allow for compaction. + compactListIfNeeded(); + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private void compactListIfNeeded() { + if (!mIsExhausted) { + mIsExhausted = true; + ObserverList.this.decrementIterationDepthAndCompactIfNeeded(); + } + } + } +} diff --git a/chromium/base/android/java/src/org/chromium/base/PathService.java b/chromium/base/android/java/src/org/chromium/base/PathService.java new file mode 100644 index 00000000000..dfda736d01a --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/PathService.java @@ -0,0 +1,24 @@ +// 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 org.chromium.base; + +/** + * This class provides java side access to the native PathService. + */ +@JNINamespace("base::android") +public abstract class PathService { + + // Must match the value of DIR_MODULE in base/base_paths.h! + public static final int DIR_MODULE = 3; + + // Prevent instantiation. + private PathService() {} + + public static void override(int what, String path) { + nativeOverride(what, path); + } + + private static native void nativeOverride(int what, String path); +} diff --git a/chromium/base/android/java/src/org/chromium/base/PathUtils.java b/chromium/base/android/java/src/org/chromium/base/PathUtils.java new file mode 100644 index 00000000000..aee5c050c34 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/PathUtils.java @@ -0,0 +1,108 @@ +// 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 org.chromium.base; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.Environment; + +import java.io.File; + +/** + * This class provides the path related methods for the native library. + */ +public abstract class PathUtils { + + private static String sDataDirectorySuffix; + private static String sWebappDirectorySuffix; + private static String sWebappCacheDirectory; + + // Prevent instantiation. + private PathUtils() {} + + /** + * Sets the suffix that should be used for the directory where private data is to be stored + * by the application. + * @param suffix The private data directory suffix. + * @see Context#getDir(String, int) + */ + public static void setPrivateDataDirectorySuffix(String suffix) { + sDataDirectorySuffix = suffix; + } + + /** + * Sets the directory info used for chrome process running in application mode. + * + * @param webappSuffix The suffix of the directory used for storing webapp-specific profile + * @param cacheDir Cache directory name for web apps. + */ + public static void setWebappDirectoryInfo(String webappSuffix, String cacheDir) { + sWebappDirectorySuffix = webappSuffix; + sWebappCacheDirectory = cacheDir; + } + + /** + * @return the private directory that is used to store application data. + */ + @CalledByNative + public static String getDataDirectory(Context appContext) { + if (sDataDirectorySuffix == null) { + throw new IllegalStateException( + "setDataDirectorySuffix must be called before getDataDirectory"); + } + return appContext.getDir(sDataDirectorySuffix, Context.MODE_PRIVATE).getPath(); + } + + /** + * @return the cache directory. + */ + @SuppressWarnings("unused") + @CalledByNative + public static String getCacheDirectory(Context appContext) { + if (ContextTypes.getInstance().getType(appContext) == ContextTypes.CONTEXT_TYPE_NORMAL) { + return appContext.getCacheDir().getPath(); + } + if (sWebappDirectorySuffix == null || sWebappCacheDirectory == null) { + throw new IllegalStateException( + "setWebappDirectoryInfo must be called before getCacheDirectory"); + } + return new File(appContext.getDir(sWebappDirectorySuffix, appContext.MODE_PRIVATE), + sWebappCacheDirectory).getPath(); + } + + /** + * @return the public downloads directory. + */ + @SuppressWarnings("unused") + @CalledByNative + private static String getDownloadsDirectory(Context appContext) { + return Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS).getPath(); + } + + /** + * @return the path to native libraries. + */ + @SuppressWarnings("unused") + @CalledByNative + private static String getNativeLibraryDirectory(Context appContext) { + ApplicationInfo ai = appContext.getApplicationInfo(); + if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 || + (ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + return ai.nativeLibraryDir; + } + + return "/system/lib/"; + } + + /** + * @return the external storage directory. + */ + @SuppressWarnings("unused") + @CalledByNative + public static String getExternalStorageDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath(); + } +} diff --git a/chromium/base/android/java/src/org/chromium/base/PowerMonitor.java b/chromium/base/android/java/src/org/chromium/base/PowerMonitor.java new file mode 100644 index 00000000000..b7a691e3ce3 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/PowerMonitor.java @@ -0,0 +1,92 @@ +// 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 org.chromium.base; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.Handler; +import android.os.Looper; + + +/** + * Integrates native PowerMonitor with the java side. + */ +@JNINamespace("base::android") +public class PowerMonitor implements ActivityStatus.StateListener { + private static final long SUSPEND_DELAY_MS = 1 * 60 * 1000; // 1 minute. + private static class LazyHolder { + private static final PowerMonitor INSTANCE = new PowerMonitor(); + } + private static PowerMonitor sInstance; + + private boolean mIsBatteryPower; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + // Asynchronous task used to fire the "paused" event to the native side 1 minute after the main + // activity transitioned to the "paused" state. This event is not sent immediately because it + // would be too aggressive. An Android activity can be in the "paused" state quite often. This + // can happen when a dialog window shows up for instance. + private static final Runnable sSuspendTask = new Runnable() { + @Override + public void run() { + nativeOnMainActivitySuspended(); + } + }; + + public static void createForTests(Context context) { + // Applications will create this once the JNI side has been fully wired up both sides. For + // tests, we just need native -> java, that is, we don't need to notify java -> native on + // creation. + sInstance = LazyHolder.INSTANCE; + } + + public static void create(Context context) { + if (sInstance == null) { + sInstance = LazyHolder.INSTANCE; + ActivityStatus.registerStateListener(sInstance); + IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + Intent batteryStatusIntent = context.registerReceiver(null, ifilter); + onBatteryChargingChanged(batteryStatusIntent); + } + } + + private PowerMonitor() { + } + + public static void onBatteryChargingChanged(Intent intent) { + if (sInstance == null) { + // We may be called by the framework intent-filter before being fully initialized. This + // is not a problem, since our constructor will check for the state later on. + return; + } + int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + // If we're not plugged, assume we're running on battery power. + sInstance.mIsBatteryPower = chargePlug != BatteryManager.BATTERY_PLUGGED_USB && + chargePlug != BatteryManager.BATTERY_PLUGGED_AC; + nativeOnBatteryChargingChanged(); + } + + @Override + public void onActivityStateChange(int newState) { + if (newState == ActivityStatus.RESUMED) { + // Remove the callback from the message loop in case it hasn't been executed yet. + mHandler.removeCallbacks(sSuspendTask); + nativeOnMainActivityResumed(); + } else if (newState == ActivityStatus.PAUSED) { + mHandler.postDelayed(sSuspendTask, SUSPEND_DELAY_MS); + } + } + + @CalledByNative + private static boolean isBatteryPower() { + return sInstance.mIsBatteryPower; + } + + private static native void nativeOnBatteryChargingChanged(); + private static native void nativeOnMainActivitySuspended(); + private static native void nativeOnMainActivityResumed(); +} diff --git a/chromium/base/android/java/src/org/chromium/base/PowerStatusReceiver.java b/chromium/base/android/java/src/org/chromium/base/PowerStatusReceiver.java new file mode 100644 index 00000000000..f36c146273f --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/PowerStatusReceiver.java @@ -0,0 +1,23 @@ +// 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 org.chromium.base; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + + +/** + * A BroadcastReceiver that listens to changes in power status and notifies + * PowerMonitor. + * It's instantiated by the framework via the application intent-filter + * declared in its manifest. + */ +public class PowerStatusReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + PowerMonitor.onBatteryChargingChanged(intent); + } +} diff --git a/chromium/base/android/java/src/org/chromium/base/SysUtils.java b/chromium/base/android/java/src/org/chromium/base/SysUtils.java new file mode 100644 index 00000000000..7af5a40069a --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/SysUtils.java @@ -0,0 +1,30 @@ +// 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. + +package org.chromium.base; + +import android.app.ActivityManager; +import android.app.ActivityManager.MemoryInfo; +import android.content.Context; +import android.os.Build; + +/** + * Exposes system related information about the current device. + */ +public class SysUtils { + private static Boolean sLowEndDevice; + + private SysUtils() { } + + /** + * @return Whether or not this device should be considered a low end device. + */ + public static boolean isLowEndDevice() { + if (sLowEndDevice == null) sLowEndDevice = nativeIsLowEndDevice(); + + return sLowEndDevice.booleanValue(); + } + + private static native boolean nativeIsLowEndDevice(); +} diff --git a/chromium/base/android/java/src/org/chromium/base/SystemMessageHandler.java b/chromium/base/android/java/src/org/chromium/base/SystemMessageHandler.java new file mode 100644 index 00000000000..fc25ff8457e --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/SystemMessageHandler.java @@ -0,0 +1,55 @@ +// 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 org.chromium.base; + +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; + +import java.util.concurrent.atomic.AtomicBoolean; + +class SystemMessageHandler extends Handler { + + private static final int TIMER_MESSAGE = 1; + private static final int DELAYED_TIMER_MESSAGE = 2; + + // Native class pointer set by the constructor of the SharedClient native class. + private int mMessagePumpDelegateNative = 0; + + private SystemMessageHandler(int messagePumpDelegateNative) { + mMessagePumpDelegateNative = messagePumpDelegateNative; + } + + @Override + public void handleMessage(Message msg) { + nativeDoRunLoopOnce(mMessagePumpDelegateNative); + } + + @SuppressWarnings("unused") + @CalledByNative + private void setTimer() { + sendEmptyMessage(TIMER_MESSAGE); + } + + @SuppressWarnings("unused") + @CalledByNative + private void setDelayedTimer(long millis) { + removeMessages(DELAYED_TIMER_MESSAGE); + sendEmptyMessageDelayed(DELAYED_TIMER_MESSAGE, millis); + } + + @SuppressWarnings("unused") + @CalledByNative + private void removeTimer() { + removeMessages(TIMER_MESSAGE); + } + + @CalledByNative + private static SystemMessageHandler create(int messagePumpDelegateNative) { + return new SystemMessageHandler(messagePumpDelegateNative); + } + + private native void nativeDoRunLoopOnce(int messagePumpDelegateNative); +} diff --git a/chromium/base/android/java/src/org/chromium/base/ThreadUtils.java b/chromium/base/android/java/src/org/chromium/base/ThreadUtils.java new file mode 100644 index 00000000000..a880ede1e3d --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/ThreadUtils.java @@ -0,0 +1,172 @@ +// 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 org.chromium.base; + +import android.os.Handler; +import android.os.Looper; +import android.os.Process; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * Helper methods to deal with threading related tasks. + */ +public class ThreadUtils { + + /** + * Run the supplied Runnable on the main thread. The method will block until the Runnable + * completes. + * + * @param r The Runnable to run. + */ + public static void runOnUiThreadBlocking(final Runnable r) { + if (runningOnUiThread()) { + r.run(); + } else { + FutureTask<Void> task = new FutureTask<Void>(r, null); + postOnUiThread(task); + try { + task.get(); + } catch (Exception e) { + throw new RuntimeException("Exception occured while waiting for runnable", e); + } + } + } + + /** + * Run the supplied Callable on the main thread, wrapping any exceptions in a RuntimeException. + * The method will block until the Callable completes. + * + * @param c The Callable to run + * @return The result of the callable + */ + public static <T> T runOnUiThreadBlockingNoException(Callable<T> c) { + try { + return runOnUiThreadBlocking(c); + } catch (ExecutionException e) { + throw new RuntimeException("Error occured waiting for callable", e); + } + } + + /** + * Run the supplied Callable on the main thread, The method will block until the Callable + * completes. + * + * @param c The Callable to run + * @return The result of the callable + * @throws ExecutionException c's exception + */ + public static <T> T runOnUiThreadBlocking(Callable<T> c) throws ExecutionException { + FutureTask<T> task = new FutureTask<T>(c); + runOnUiThread(task); + try { + return task.get(); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted waiting for callable", e); + } + } + + /** + * Run the supplied FutureTask on the main thread. The method will block only if the current + * thread is the main thread. + * + * @param task The FutureTask to run + * @return The queried task (to aid inline construction) + */ + public static <T> FutureTask<T> runOnUiThread(FutureTask<T> task) { + if (runningOnUiThread()) { + task.run(); + } else { + postOnUiThread(task); + } + return task; + } + + /** + * Run the supplied Callable on the main thread. The method will block only if the current + * thread is the main thread. + * + * @param c The Callable to run + * @return A FutureTask wrapping the callable to retrieve results + */ + public static <T> FutureTask<T> runOnUiThread(Callable<T> c) { + return runOnUiThread(new FutureTask<T>(c)); + } + + /** + * Run the supplied Runnable on the main thread. The method will block only if the current + * thread is the main thread. + * + * @param r The Runnable to run + */ + public static void runOnUiThread(Runnable r) { + if (runningOnUiThread()) { + r.run(); + } else { + LazyHolder.sUiThreadHandler.post(r); + } + } + + /** + * Post the supplied FutureTask to run on the main thread. The method will not block, even if + * called on the UI thread. + * + * @param task The FutureTask to run + * @return The queried task (to aid inline construction) + */ + public static <T> FutureTask<T> postOnUiThread(FutureTask<T> task) { + LazyHolder.sUiThreadHandler.post(task); + return task; + } + + /** + * Post the supplied Runnable to run on the main thread. The method will not block, even if + * called on the UI thread. + * + * @param task The Runnable to run + */ + public static void postOnUiThread(Runnable r) { + LazyHolder.sUiThreadHandler.post(r); + } + + /** + * Post the supplied Runnable to run on the main thread after the given amount of time. The + * method will not block, even if called on the UI thread. + * + * @param task The Runnable to run + * @param delayMillis The delay in milliseconds until the Runnable will be run + */ + public static void postOnUiThreadDelayed(Runnable r, long delayMillis) { + LazyHolder.sUiThreadHandler.postDelayed(r, delayMillis); + } + + /** + * Asserts that the current thread is running on the main thread. + */ + public static void assertOnUiThread() { + assert runningOnUiThread(); + } + + /** + * @return true iff the current thread is the main (UI) thread. + */ + public static boolean runningOnUiThread() { + return Looper.getMainLooper() == Looper.myLooper(); + } + + /** + * Set thread priority to audio. + */ + @CalledByNative + public static void setThreadPriorityAudio(int tid) { + Process.setThreadPriority(tid, Process.THREAD_PRIORITY_AUDIO); + } + + private static class LazyHolder { + private static Handler sUiThreadHandler = new Handler(Looper.getMainLooper()); + } +} diff --git a/chromium/base/android/java/src/org/chromium/base/WeakContext.java b/chromium/base/android/java/src/org/chromium/base/WeakContext.java new file mode 100644 index 00000000000..d660cc99406 --- /dev/null +++ b/chromium/base/android/java/src/org/chromium/base/WeakContext.java @@ -0,0 +1,45 @@ +// 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 org.chromium.base; + +import android.content.Context; + +import java.lang.ref.WeakReference; +import java.util.concurrent.Callable; + +// Holds a WeakReference to Context to allow it to be GC'd. +// Also provides utility functions to getSystemService from the UI or any +// other thread (may return null, if the Context has been nullified). +public class WeakContext { + private static WeakReference<Context> sWeakContext; + + public static void initializeWeakContext(final Context context) { + sWeakContext = new WeakReference<Context>(context); + } + + public static Context getContext() { + return sWeakContext.get(); + } + + // Returns a system service. May be called from any thread. + // If necessary, it will send a message to the main thread to acquire the + // service, and block waiting for it to complete. + // May return null if context is no longer available. + public static Object getSystemService(final String name) { + final Context context = sWeakContext.get(); + if (context == null) { + return null; + } + if (ThreadUtils.runningOnUiThread()) { + return context.getSystemService(name); + } + return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Object>() { + @Override + public Object call() { + return context.getSystemService(name); + } + }); + } +} diff --git a/chromium/base/android/java_handler_thread.cc b/chromium/base/android/java_handler_thread.cc new file mode 100644 index 00000000000..0528fe74ba9 --- /dev/null +++ b/chromium/base/android/java_handler_thread.cc @@ -0,0 +1,62 @@ +// 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/java_handler_thread.h" + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_restrictions.h" +#include "jni/JavaHandlerThread_jni.h" + +namespace base { + +namespace android { + +JavaHandlerThread::JavaHandlerThread(const char* name) { + JNIEnv* env = base::android::AttachCurrentThread(); + + java_thread_.Reset(Java_JavaHandlerThread_create( + env, ConvertUTF8ToJavaString(env, name).Release())); +} + +JavaHandlerThread::~JavaHandlerThread() { +} + +void JavaHandlerThread::Start() { + // Check the thread has not already been started. + DCHECK(!message_loop_); + + JNIEnv* env = base::android::AttachCurrentThread(); + base::WaitableEvent initialize_event(false, false); + Java_JavaHandlerThread_start(env, + java_thread_.obj(), + reinterpret_cast<jint>(this), + reinterpret_cast<jint>(&initialize_event)); + // Wait for thread to be initialized so it is ready to be used when Start + // returns. + base::ThreadRestrictions::ScopedAllowWait wait_allowed; + initialize_event.Wait(); +} + +void JavaHandlerThread::Stop() { +} + +void JavaHandlerThread::InitializeThread(JNIEnv* env, jobject obj, jint event) { + // TYPE_JAVA to get the Android java style message loop. + message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_JAVA)); + static_cast<MessageLoopForUI*>(message_loop_.get())->Start(); + reinterpret_cast<base::WaitableEvent*>(event)->Signal(); +} + +// static +bool JavaHandlerThread::RegisterBindings(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/java_handler_thread.h b/chromium/base/android/java_handler_thread.h new file mode 100644 index 00000000000..9f66d660c84 --- /dev/null +++ b/chromium/base/android/java_handler_thread.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef BASE_THREADING_JAVA_THREAD_H_ +#define BASE_THREADING_JAVA_THREAD_H_ + +#include <jni.h> + +#include "base/android/scoped_java_ref.h" +#include "base/memory/scoped_ptr.h" + +namespace base { + +class MessageLoop; +class WaitableEvent; + +namespace android { + +// A Java Thread with a native message loop. To run tasks, post them +// to the message loop and they will be scheduled along with Java tasks +// on the thread. +// This is useful for callbacks where the receiver expects a thread +// with a prepared Looper. +class BASE_EXPORT JavaHandlerThread { + public: + JavaHandlerThread(const char* name); + virtual ~JavaHandlerThread(); + + base::MessageLoop* message_loop() const { return message_loop_.get(); } + void Start(); + void Stop(); + + // Called from java on the newly created thread. + // Start() will not return before this methods has finished. + void InitializeThread(JNIEnv* env, jobject obj, jint event); + + static bool RegisterBindings(JNIEnv* env); + + private: + scoped_ptr<base::MessageLoop> message_loop_; + ScopedJavaGlobalRef<jobject> java_thread_; +}; + +} // namespace android +} // namespace base + +#endif // BASE_THREADING_JAVA_THREAD_H_ diff --git a/chromium/base/android/javatests/src/org/chromium/base/ContextTypesTest.java b/chromium/base/android/javatests/src/org/chromium/base/ContextTypesTest.java new file mode 100644 index 00000000000..a26b3177d83 --- /dev/null +++ b/chromium/base/android/javatests/src/org/chromium/base/ContextTypesTest.java @@ -0,0 +1,43 @@ +// 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. + +package org.chromium.base; + +import android.content.Context; +import android.test.AndroidTestCase; +import android.test.mock.MockContext; +import android.test.suitebuilder.annotation.SmallTest; + +import static org.chromium.base.ContextTypes.CONTEXT_TYPE_NORMAL; +import static org.chromium.base.ContextTypes.CONTEXT_TYPE_WEBAPP; + +public class ContextTypesTest extends AndroidTestCase { + + @SmallTest + public void testReturnsExpectedType() { + ContextTypes contextTypes = ContextTypes.getInstance(); + Context normal = new MockContext(); + Context webapp = new MockContext(); + contextTypes.put(normal, CONTEXT_TYPE_NORMAL); + contextTypes.put(webapp, CONTEXT_TYPE_WEBAPP); + assertEquals(CONTEXT_TYPE_NORMAL, contextTypes.getType(normal)); + assertEquals(CONTEXT_TYPE_WEBAPP, contextTypes.getType(webapp)); + } + + @SmallTest + public void testAbsentContextReturnsNormalType() { + assertEquals(CONTEXT_TYPE_NORMAL, ContextTypes.getInstance().getType(new MockContext())); + } + + @SmallTest + public void testPutInvalidTypeThrowsException() { + boolean exceptionThrown = false; + try { + ContextTypes.getInstance().put(new MockContext(), -1); + } catch (IllegalArgumentException e) { + exceptionThrown = true; + } + assertTrue(exceptionThrown); + } +} diff --git a/chromium/base/android/javatests/src/org/chromium/base/ObserverListTest.java b/chromium/base/android/javatests/src/org/chromium/base/ObserverListTest.java new file mode 100644 index 00000000000..10c898c2a3a --- /dev/null +++ b/chromium/base/android/javatests/src/org/chromium/base/ObserverListTest.java @@ -0,0 +1,180 @@ +// 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. + +package org.chromium.base; + +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.chromium.base.test.util.Feature; + +import java.lang.Iterable; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Tests for (@link ObserverList}. + */ +public class ObserverListTest extends InstrumentationTestCase { + interface Observer { + void observe(int x); + } + + private static class Foo implements Observer { + private final int mScalar; + private int mTotal = 0; + + Foo(int scalar) { + mScalar = scalar; + } + + @Override + public void observe(int x) { + mTotal += x * mScalar; + } + } + + /** + * An observer which add a given Observer object to the list when observe is called. + */ + private static class FooAdder implements Observer { + private final ObserverList<Observer> mList; + private final Observer mLucky; + + FooAdder(ObserverList<Observer> list, Observer oblivious) { + mList = list; + mLucky = oblivious; + } + + @Override + public void observe(int x) { + mList.addObserver(mLucky); + } + } + + /** + * An observer which removes a given Observer object from the list when observe is called. + */ + private static class FooRemover implements Observer { + private final ObserverList<Observer> mList; + private final Observer mDoomed; + + FooRemover(ObserverList<Observer> list, Observer innocent) { + mList = list; + mDoomed = innocent; + } + + @Override + public void observe(int x) { + mList.removeObserver(mDoomed); + } + } + + private static <T> int getSizeOfIterable(Iterable<T> iterable) { + int num = 0; + for (T el : iterable) + num++; + return num; + } + + @SmallTest + @Feature({"Android-AppBase"}) + public void testRemoveWhileIteration() { + ObserverList<Observer> observerList = new ObserverList<Observer>(); + Foo a = new Foo(1); + Foo b = new Foo(-1); + Foo c = new Foo(1); + Foo d = new Foo(-1); + Foo e = new Foo(-1); + FooRemover evil = new FooRemover(observerList, c); + + observerList.addObserver(a); + observerList.addObserver(b); + + for (Observer obs : observerList) + obs.observe(10); + + // Removing an observer not in the list should do nothing. + observerList.removeObserver(e); + + observerList.addObserver(evil); + observerList.addObserver(c); + observerList.addObserver(d); + + for (Observer obs : observerList) + obs.observe(10); + + // observe should be called twice on a. + assertEquals(20, a.mTotal); + // observe should be called twice on b. + assertEquals(-20, b.mTotal); + // evil removed c from the observerList before it got any callbacks. + assertEquals(0, c.mTotal); + // observe should be called once on d. + assertEquals(-10, d.mTotal); + // e was never added to the list, observe should not be called. + assertEquals(0, e.mTotal); + } + + @SmallTest + @Feature({"Android-AppBase"}) + public void testAddWhileIteration() { + ObserverList<Observer> observerList = new ObserverList<Observer>(); + Foo a = new Foo(1); + Foo b = new Foo(-1); + Foo c = new Foo(1); + FooAdder evil = new FooAdder(observerList, c); + + observerList.addObserver(evil); + observerList.addObserver(a); + observerList.addObserver(b); + + for (Observer obs : observerList) + obs.observe(10); + + assertTrue(observerList.hasObserver(c)); + assertEquals(10, a.mTotal); + assertEquals(-10, b.mTotal); + assertEquals(0, c.mTotal); + } + + @SmallTest + @Feature({"Android-AppBase"}) + public void testIterator() { + ObserverList<Integer> observerList = new ObserverList<Integer>(); + observerList.addObserver(5); + observerList.addObserver(10); + observerList.addObserver(15); + assertEquals(3, getSizeOfIterable(observerList)); + + observerList.removeObserver(10); + assertEquals(2, getSizeOfIterable(observerList)); + + Iterator<Integer> it = observerList.iterator(); + assertTrue(it.hasNext()); + assertTrue(5 == it.next()); + assertTrue(it.hasNext()); + assertTrue(15 == it.next()); + assertFalse(it.hasNext()); + + boolean removeExceptionThrown = false; + try { + it.remove(); + fail("Expecting UnsupportedOperationException to be thrown here."); + } catch (UnsupportedOperationException e) { + removeExceptionThrown = true; + } + assertTrue(removeExceptionThrown); + assertEquals(2, getSizeOfIterable(observerList)); + + boolean noElementExceptionThrown = false; + try { + it.next(); + fail("Expecting NoSuchElementException to be thrown here."); + } catch (NoSuchElementException e) { + noElementExceptionThrown = true; + } + assertTrue(noElementExceptionThrown); + } +} diff --git a/chromium/base/android/jni_android.cc b/chromium/base/android/jni_android.cc new file mode 100644 index 00000000000..fc89b5403f8 --- /dev/null +++ b/chromium/base/android/jni_android.cc @@ -0,0 +1,323 @@ +// 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. + +#include "base/android/jni_android.h" + +#include <map> + +#include "base/android/build_info.h" +#include "base/android/jni_string.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/platform_thread.h" + +namespace { +using base::android::GetClass; +using base::android::MethodID; +using base::android::ScopedJavaLocalRef; + +struct MethodIdentifier { + const char* class_name; + const char* method; + const char* jni_signature; + + bool operator<(const MethodIdentifier& other) const { + int r = strcmp(class_name, other.class_name); + if (r < 0) { + return true; + } else if (r > 0) { + return false; + } + + r = strcmp(method, other.method); + if (r < 0) { + return true; + } else if (r > 0) { + return false; + } + + return strcmp(jni_signature, other.jni_signature) < 0; + } +}; + +typedef std::map<MethodIdentifier, jmethodID> MethodIDMap; + +const base::subtle::AtomicWord kUnlocked = 0; +const base::subtle::AtomicWord kLocked = 1; +base::subtle::AtomicWord g_method_id_map_lock = kUnlocked; +JavaVM* g_jvm = NULL; +// Leak the global app context, as it is used from a non-joinable worker thread +// that may still be running at shutdown. There is no harm in doing this. +base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject> >::Leaky + g_application_context = LAZY_INSTANCE_INITIALIZER; +base::LazyInstance<MethodIDMap> g_method_id_map = LAZY_INSTANCE_INITIALIZER; + +std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { + ScopedJavaLocalRef<jclass> throwable_clazz = + GetClass(env, "java/lang/Throwable"); + jmethodID throwable_printstacktrace = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, throwable_clazz.obj(), "printStackTrace", + "(Ljava/io/PrintStream;)V"); + + // Create an instance of ByteArrayOutputStream. + ScopedJavaLocalRef<jclass> bytearray_output_stream_clazz = + GetClass(env, "java/io/ByteArrayOutputStream"); + jmethodID bytearray_output_stream_constructor = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, bytearray_output_stream_clazz.obj(), "<init>", "()V"); + jmethodID bytearray_output_stream_tostring = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, bytearray_output_stream_clazz.obj(), "toString", + "()Ljava/lang/String;"); + ScopedJavaLocalRef<jobject> bytearray_output_stream(env, + env->NewObject(bytearray_output_stream_clazz.obj(), + bytearray_output_stream_constructor)); + + // Create an instance of PrintStream. + ScopedJavaLocalRef<jclass> printstream_clazz = + GetClass(env, "java/io/PrintStream"); + jmethodID printstream_constructor = + MethodID::Get<MethodID::TYPE_INSTANCE>( + env, printstream_clazz.obj(), "<init>", + "(Ljava/io/OutputStream;)V"); + ScopedJavaLocalRef<jobject> printstream(env, + env->NewObject(printstream_clazz.obj(), printstream_constructor, + bytearray_output_stream.obj())); + + // Call Throwable.printStackTrace(PrintStream) + env->CallVoidMethod(java_throwable, throwable_printstacktrace, + printstream.obj()); + + // Call ByteArrayOutputStream.toString() + ScopedJavaLocalRef<jstring> exception_string( + env, static_cast<jstring>( + env->CallObjectMethod(bytearray_output_stream.obj(), + bytearray_output_stream_tostring))); + + return ConvertJavaStringToUTF8(exception_string); +} + +} // namespace + +namespace base { +namespace android { + +JNIEnv* AttachCurrentThread() { + DCHECK(g_jvm); + JNIEnv* env = NULL; + jint ret = g_jvm->AttachCurrentThread(&env, NULL); + DCHECK_EQ(JNI_OK, ret); + return env; +} + +void DetachFromVM() { + // Ignore the return value, if the thread is not attached, DetachCurrentThread + // will fail. But it is ok as the native thread may never be attached. + if (g_jvm) + g_jvm->DetachCurrentThread(); +} + +void InitVM(JavaVM* vm) { + DCHECK(!g_jvm); + g_jvm = vm; +} + +void InitApplicationContext(const JavaRef<jobject>& context) { + DCHECK(g_application_context.Get().is_null()); + g_application_context.Get().Reset(context); +} + +const jobject GetApplicationContext() { + DCHECK(!g_application_context.Get().is_null()); + return g_application_context.Get().obj(); +} + +ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, const char* class_name) { + jclass clazz = env->FindClass(class_name); + CHECK(!ClearException(env) && clazz) << "Failed to find class " << class_name; + return ScopedJavaLocalRef<jclass>(env, clazz); +} + +bool HasClass(JNIEnv* env, const char* class_name) { + ScopedJavaLocalRef<jclass> clazz(env, env->FindClass(class_name)); + if (!clazz.obj()) { + ClearException(env); + return false; + } + bool error = ClearException(env); + DCHECK(!error); + return true; +} + +template<MethodID::Type type> +jmethodID MethodID::Get(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature) { + jmethodID id = type == TYPE_STATIC ? + env->GetStaticMethodID(clazz, method_name, jni_signature) : + env->GetMethodID(clazz, method_name, jni_signature); + CHECK(base::android::ClearException(env) || id) << + "Failed to find " << + (type == TYPE_STATIC ? "static " : "") << + "method " << method_name << " " << jni_signature; + return id; +} + +// If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call +// into ::Get() above. If there's a race, it's ok since the values are the same +// (and the duplicated effort will happen only once). +template<MethodID::Type type> +jmethodID MethodID::LazyGet(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature, + base::subtle::AtomicWord* atomic_method_id) { + COMPILE_ASSERT(sizeof(subtle::AtomicWord) >= sizeof(jmethodID), + AtomicWord_SmallerThan_jMethodID); + subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_method_id); + if (value) + return reinterpret_cast<jmethodID>(value); + jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature); + base::subtle::Release_Store( + atomic_method_id, reinterpret_cast<subtle::AtomicWord>(id)); + return id; +} + +// Various template instantiations. +template jmethodID MethodID::Get<MethodID::TYPE_STATIC>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::Get<MethodID::TYPE_INSTANCE>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); + +template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); + +jfieldID GetFieldID(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature) { + jfieldID field_id = env->GetFieldID(clazz.obj(), field_name, jni_signature); + CHECK(!ClearException(env) && field_id) << "Failed to find field " << + field_name << " " << jni_signature; + return field_id; +} + +bool HasField(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature) { + jfieldID field_id = env->GetFieldID(clazz.obj(), field_name, jni_signature); + if (!field_id) { + ClearException(env); + return false; + } + bool error = ClearException(env); + DCHECK(!error); + return true; +} + +jfieldID GetStaticFieldID(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature) { + jfieldID field_id = + env->GetStaticFieldID(clazz.obj(), field_name, jni_signature); + CHECK(!ClearException(env) && field_id) << "Failed to find static field " << + field_name << " " << jni_signature; + return field_id; +} + +jmethodID GetMethodIDFromClassName(JNIEnv* env, + const char* class_name, + const char* method, + const char* jni_signature) { + MethodIdentifier key; + key.class_name = class_name; + key.method = method; + key.jni_signature = jni_signature; + + MethodIDMap* map = g_method_id_map.Pointer(); + bool found = false; + + while (base::subtle::Acquire_CompareAndSwap(&g_method_id_map_lock, + kUnlocked, + kLocked) != kUnlocked) { + base::PlatformThread::YieldCurrentThread(); + } + MethodIDMap::const_iterator iter = map->find(key); + if (iter != map->end()) { + found = true; + } + base::subtle::Release_Store(&g_method_id_map_lock, kUnlocked); + + // Addition to the map does not invalidate this iterator. + if (found) { + return iter->second; + } + + ScopedJavaLocalRef<jclass> clazz(env, env->FindClass(class_name)); + jmethodID id = MethodID::Get<MethodID::TYPE_INSTANCE>( + env, clazz.obj(), method, jni_signature); + + while (base::subtle::Acquire_CompareAndSwap(&g_method_id_map_lock, + kUnlocked, + kLocked) != kUnlocked) { + base::PlatformThread::YieldCurrentThread(); + } + // Another thread may have populated the map already. + std::pair<MethodIDMap::const_iterator, bool> result = + map->insert(std::make_pair(key, id)); + DCHECK_EQ(id, result.first->second); + base::subtle::Release_Store(&g_method_id_map_lock, kUnlocked); + + return id; +} + +bool HasException(JNIEnv* env) { + return env->ExceptionCheck() != JNI_FALSE; +} + +bool ClearException(JNIEnv* env) { + if (!HasException(env)) + return false; + env->ExceptionDescribe(); + env->ExceptionClear(); + return true; +} + +void CheckException(JNIEnv* env) { + if (!HasException(env)) return; + + // Exception has been found, might as well tell breakpad about it. + jthrowable java_throwable = env->ExceptionOccurred(); + if (!java_throwable) { + // Do nothing but return false. + CHECK(false); + } + + // Clear the pending exception, since a local reference is now held. + env->ExceptionDescribe(); + env->ExceptionClear(); + + // Set the exception_string in BuildInfo so that breakpad can read it. + // RVO should avoid any extra copies of the exception string. + base::android::BuildInfo::GetInstance()->set_java_exception_info( + GetJavaExceptionInfo(env, java_throwable)); + + // Now, feel good about it and die. + CHECK(false); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/jni_android.h b/chromium/base/android/jni_android.h new file mode 100644 index 00000000000..e8f68acfb7c --- /dev/null +++ b/chromium/base/android/jni_android.h @@ -0,0 +1,132 @@ +// 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. + +#ifndef BASE_ANDROID_JNI_ANDROID_H_ +#define BASE_ANDROID_JNI_ANDROID_H_ + +#include <jni.h> +#include <sys/types.h> + +#include "base/android/scoped_java_ref.h" +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" + +namespace base { +namespace android { + +// Used to mark symbols to be exported in a shared library's symbol table. +#define JNI_EXPORT __attribute__ ((visibility("default"))) + +// Contains the registration method information for initializing JNI bindings. +struct RegistrationMethod { + const char* name; + bool (*func)(JNIEnv* env); +}; + +// Attach the current thread to the VM (if necessary) and return the JNIEnv*. +BASE_EXPORT JNIEnv* AttachCurrentThread(); + +// Detach the current thread from VM if it is attached. +BASE_EXPORT void DetachFromVM(); + +// Initializes the global JVM. It is not necessarily called before +// InitApplicationContext(). +BASE_EXPORT void InitVM(JavaVM* vm); + +// Initializes the global application context object. The |context| can be any +// valid reference to the application context. Internally holds a global ref to +// the context. InitVM and InitApplicationContext maybe called in either order. +BASE_EXPORT void InitApplicationContext(const JavaRef<jobject>& context); + +// Gets a global ref to the application context set with +// InitApplicationContext(). Ownership is retained by the function - the caller +// must NOT release it. +const BASE_EXPORT jobject GetApplicationContext(); + +// Finds the class named |class_name| and returns it. +// Use this method instead of invoking directly the JNI FindClass method (to +// prevent leaking local references). +// This method triggers a fatal assertion if the class could not be found. +// Use HasClass if you need to check whether the class exists. +BASE_EXPORT ScopedJavaLocalRef<jclass> GetClass(JNIEnv* env, + const char* class_name); + +// Returns true iff the class |class_name| could be found. +BASE_EXPORT bool HasClass(JNIEnv* env, const char* class_name); + +// This class is a wrapper for JNIEnv Get(Static)MethodID. +class BASE_EXPORT MethodID { + public: + enum Type { + TYPE_STATIC, + TYPE_INSTANCE, + }; + + // Returns the method ID for the method with the specified name and signature. + // This method triggers a fatal assertion if the method could not be found. + template<Type type> + static jmethodID Get(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature); + + // The caller is responsible to zero-initialize |atomic_method_id|. + // It's fine to simultaneously call this on multiple threads referencing the + // same |atomic_method_id|. + template<Type type> + static jmethodID LazyGet(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature, + base::subtle::AtomicWord* atomic_method_id); +}; + +// Gets the method ID from the class name. Clears the pending Java exception +// and returns NULL if the method is not found. Caches results. Note that +// MethodID::Get() above avoids a class lookup, but does not cache results. +// Strings passed to this function are held in the cache and MUST remain valid +// beyond the duration of all future calls to this function, across all +// threads. In practice, this means that the function should only be used with +// string constants. +BASE_EXPORT jmethodID GetMethodIDFromClassName(JNIEnv* env, + const char* class_name, + const char* method, + const char* jni_signature); + +// Gets the field ID for a class field. +// This method triggers a fatal assertion if the field could not be found. +BASE_EXPORT jfieldID GetFieldID(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature); + +// Returns true if |clazz| as a field with the given name and signature. +// TODO(jcivelli): Determine whether we explicitly have to pass the environment. +BASE_EXPORT bool HasField(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature); + +// Gets the field ID for a static class field. +// This method triggers a fatal assertion if the field could not be found. +BASE_EXPORT jfieldID GetStaticFieldID(JNIEnv* env, + const JavaRef<jclass>& clazz, + const char* field_name, + const char* jni_signature); + +// Returns true if an exception is pending in the provided JNIEnv*. +BASE_EXPORT bool HasException(JNIEnv* env); + +// If an exception is pending in the provided JNIEnv*, this function clears it +// and returns true. +BASE_EXPORT bool ClearException(JNIEnv* env); + +// This function will call CHECK() macro if there's any pending exception. +BASE_EXPORT void CheckException(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_ANDROID_H_ diff --git a/chromium/base/android/jni_android_unittest.cc b/chromium/base/android/jni_android_unittest.cc new file mode 100644 index 00000000000..920b3956590 --- /dev/null +++ b/chromium/base/android/jni_android_unittest.cc @@ -0,0 +1,141 @@ +// 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. + +#include "base/android/jni_android.h" + +#include "base/at_exit.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { + +const char kJavaLangObject[] = "java/lang/Object"; +const char kGetClass[] = "getClass"; +const char kToString[] = "toString"; +const char kReturningJavaLangClass[] = "()Ljava/lang/Class;"; +const char kReturningJavaLangString[] = "()Ljava/lang/String;"; + +const char* g_last_method; +const char* g_last_jni_signature; +jmethodID g_last_method_id; + +const JNINativeInterface* g_previous_functions; + +jmethodID GetMethodIDWrapper(JNIEnv* env, jclass clazz, const char* method, + const char* jni_signature) { + g_last_method = method; + g_last_jni_signature = jni_signature; + g_last_method_id = g_previous_functions->GetMethodID(env, clazz, method, + jni_signature); + return g_last_method_id; +} + +} // namespace + +class JNIAndroidTest : public testing::Test { + protected: + virtual void SetUp() { + JNIEnv* env = AttachCurrentThread(); + g_previous_functions = env->functions; + hooked_functions = *g_previous_functions; + env->functions = &hooked_functions; + hooked_functions.GetMethodID = &GetMethodIDWrapper; + } + + virtual void TearDown() { + JNIEnv* env = AttachCurrentThread(); + env->functions = g_previous_functions; + Reset(); + } + + void Reset() { + g_last_method = 0; + g_last_jni_signature = 0; + g_last_method_id = NULL; + } + // Needed to cleanup the cached method map in the implementation between + // runs (e.g. if using --gtest_repeat) + base::ShadowingAtExitManager exit_manager; + // From JellyBean release, the instance of this struct provided in JNIEnv is + // read-only, so we deep copy it to allow individual functions to be hooked. + JNINativeInterface hooked_functions; +}; + +TEST_F(JNIAndroidTest, GetMethodIDFromClassNameCaching) { + JNIEnv* env = AttachCurrentThread(); + + Reset(); + jmethodID id1 = GetMethodIDFromClassName(env, kJavaLangObject, kGetClass, + kReturningJavaLangClass); + EXPECT_STREQ(kGetClass, g_last_method); + EXPECT_STREQ(kReturningJavaLangClass, g_last_jni_signature); + EXPECT_EQ(g_last_method_id, id1); + + Reset(); + jmethodID id2 = GetMethodIDFromClassName(env, kJavaLangObject, kGetClass, + kReturningJavaLangClass); + EXPECT_STREQ(0, g_last_method); + EXPECT_STREQ(0, g_last_jni_signature); + EXPECT_EQ(NULL, g_last_method_id); + EXPECT_EQ(id1, id2); + + Reset(); + jmethodID id3 = GetMethodIDFromClassName(env, kJavaLangObject, kToString, + kReturningJavaLangString); + EXPECT_STREQ(kToString, g_last_method); + EXPECT_STREQ(kReturningJavaLangString, g_last_jni_signature); + EXPECT_EQ(g_last_method_id, id3); +} + +namespace { + +base::subtle::AtomicWord g_atomic_id = 0; +int LazyMethodIDCall(JNIEnv* env, jclass clazz, int p) { + jmethodID id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, clazz, + "abs", + "(I)I", + &g_atomic_id); + + return env->CallStaticIntMethod(clazz, id, p); +} + +int MethodIDCall(JNIEnv* env, jclass clazz, jmethodID id, int p) { + return env->CallStaticIntMethod(clazz, id, p); +} + +} // namespace + +TEST(JNIAndroidMicrobenchmark, MethodId) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jclass> clazz(GetClass(env, "java/lang/Math")); + base::Time start_lazy = base::Time::Now(); + int o = 0; + for (int i = 0; i < 1024; ++i) + o += LazyMethodIDCall(env, clazz.obj(), i); + base::Time end_lazy = base::Time::Now(); + + jmethodID id = reinterpret_cast<jmethodID>(g_atomic_id); + base::Time start = base::Time::Now(); + for (int i = 0; i < 1024; ++i) + o += MethodIDCall(env, clazz.obj(), id, i); + base::Time end = base::Time::Now(); + + // On a Galaxy Nexus, results were in the range of: + // JNI LazyMethodIDCall (us) 1984 + // JNI MethodIDCall (us) 1861 + LOG(ERROR) << "JNI LazyMethodIDCall (us) " << + base::TimeDelta(end_lazy - start_lazy).InMicroseconds(); + LOG(ERROR) << "JNI MethodIDCall (us) " << + base::TimeDelta(end - start).InMicroseconds(); + LOG(ERROR) << "JNI " << o; +} + + +} // namespace android +} // namespace base diff --git a/chromium/base/android/jni_array.cc b/chromium/base/android/jni_array.cc new file mode 100644 index 00000000000..bbe00c61290 --- /dev/null +++ b/chromium/base/android/jni_array.cc @@ -0,0 +1,186 @@ +// 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. + +#include "base/android/jni_array.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/logging.h" + +namespace base { +namespace android { + +ScopedJavaLocalRef<jbyteArray> ToJavaByteArray( + JNIEnv* env, const uint8* bytes, size_t len) { + jbyteArray byte_array = env->NewByteArray(len); + CheckException(env); + DCHECK(byte_array); + + jbyte* elements = env->GetByteArrayElements(byte_array, NULL); + memcpy(elements, bytes, len); + env->ReleaseByteArrayElements(byte_array, elements, 0); + CheckException(env); + + return ScopedJavaLocalRef<jbyteArray>(env, byte_array); +} + +ScopedJavaLocalRef<jlongArray> ToJavaLongArray( + JNIEnv* env, const int64* longs, size_t len) { + jlongArray long_array = env->NewLongArray(len); + CheckException(env); + DCHECK(long_array); + + jlong* elements = env->GetLongArrayElements(long_array, NULL); + memcpy(elements, longs, len * sizeof(*longs)); + env->ReleaseLongArrayElements(long_array, elements, 0); + CheckException(env); + + return ScopedJavaLocalRef<jlongArray>(env, long_array); +} + +// Returns a new Java long array converted from the given int64 array. +BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray( + JNIEnv* env, const std::vector<int64>& longs) { + return ToJavaLongArray(env, longs.begin(), longs.size()); +} + +ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray( + JNIEnv* env, const std::vector<std::string>& v) { + ScopedJavaLocalRef<jclass> byte_array_clazz = GetClass(env, "[B"); + jobjectArray joa = env->NewObjectArray(v.size(), + byte_array_clazz.obj(), NULL); + CheckException(env); + + for (size_t i = 0; i < v.size(); ++i) { + ScopedJavaLocalRef<jbyteArray> byte_array = ToJavaByteArray(env, + reinterpret_cast<const uint8*>(v[i].data()), v[i].length()); + env->SetObjectArrayElement(joa, i, byte_array.obj()); + } + return ScopedJavaLocalRef<jobjectArray>(env, joa); +} + +ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<std::string>& v) { + ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String"); + jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL); + CheckException(env); + + for (size_t i = 0; i < v.size(); ++i) { + ScopedJavaLocalRef<jstring> item = ConvertUTF8ToJavaString(env, v[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef<jobjectArray>(env, joa); +} + +ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<string16>& v) { + ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String"); + jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL); + CheckException(env); + + for (size_t i = 0; i < v.size(); ++i) { + ScopedJavaLocalRef<jstring> item = ConvertUTF16ToJavaString(env, v[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef<jobjectArray>(env, joa); +} + +void AppendJavaStringArrayToStringVector(JNIEnv* env, + jobjectArray array, + std::vector<string16>* out) { + DCHECK(out); + if (!array) + return; + jsize len = env->GetArrayLength(array); + size_t back = out->size(); + out->resize(back + len); + for (jsize i = 0; i < len; ++i) { + ScopedJavaLocalRef<jstring> str(env, + static_cast<jstring>(env->GetObjectArrayElement(array, i))); + ConvertJavaStringToUTF16(env, str.obj(), &((*out)[back + i])); + } +} + +void AppendJavaStringArrayToStringVector(JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out) { + DCHECK(out); + if (!array) + return; + jsize len = env->GetArrayLength(array); + size_t back = out->size(); + out->resize(back + len); + for (jsize i = 0; i < len; ++i) { + ScopedJavaLocalRef<jstring> str(env, + static_cast<jstring>(env->GetObjectArrayElement(array, i))); + ConvertJavaStringToUTF8(env, str.obj(), &((*out)[back + i])); + } +} + +void AppendJavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8>* out) { + DCHECK(out); + if (!byte_array) + return; + jsize len = env->GetArrayLength(byte_array); + jbyte* bytes = env->GetByteArrayElements(byte_array, NULL); + out->insert(out->end(), bytes, bytes + len); + env->ReleaseByteArrayElements(byte_array, bytes, JNI_ABORT); +} + +void JavaByteArrayToByteVector(JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8>* out) { + DCHECK(out); + out->clear(); + AppendJavaByteArrayToByteVector(env, byte_array, out); +} + +void JavaIntArrayToIntVector(JNIEnv* env, + jintArray int_array, + std::vector<int>* out) { + DCHECK(out); + out->clear(); + jsize len = env->GetArrayLength(int_array); + jint* ints = env->GetIntArrayElements(int_array, NULL); + for (jsize i = 0; i < len; ++i) { + out->push_back(static_cast<int>(ints[i])); + } + env->ReleaseIntArrayElements(int_array, ints, JNI_ABORT); +} + +void JavaFloatArrayToFloatVector(JNIEnv* env, + jfloatArray float_array, + std::vector<float>* out) { + DCHECK(out); + out->clear(); + jsize len = env->GetArrayLength(float_array); + jfloat* floats = env->GetFloatArrayElements(float_array, NULL); + for (jsize i = 0; i < len; ++i) { + out->push_back(static_cast<float>(floats[i])); + } + env->ReleaseFloatArrayElements(float_array, floats, JNI_ABORT); +} + +void JavaArrayOfByteArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out) { + DCHECK(out); + out->clear(); + jsize len = env->GetArrayLength(array); + out->resize(len); + for (jsize i = 0; i < len; ++i) { + jbyteArray bytes_array = static_cast<jbyteArray>( + env->GetObjectArrayElement(array, i)); + jsize bytes_len = env->GetArrayLength(bytes_array); + jbyte* bytes = env->GetByteArrayElements(bytes_array, NULL); + (*out)[i].assign(reinterpret_cast<const char*>(bytes), bytes_len); + env->ReleaseByteArrayElements(bytes_array, bytes, JNI_ABORT); + } +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/jni_array.h b/chromium/base/android/jni_array.h new file mode 100644 index 00000000000..efa6aa8ffe8 --- /dev/null +++ b/chromium/base/android/jni_array.h @@ -0,0 +1,86 @@ +// 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. + +#ifndef BASE_ANDROID_JNI_ARRAY_H_ +#define BASE_ANDROID_JNI_ARRAY_H_ + +#include <jni.h> +#include <string> +#include <vector> + +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/strings/string16.h" + +namespace base { +namespace android { + +// Returns a new Java byte array converted from the given bytes array. +BASE_EXPORT ScopedJavaLocalRef<jbyteArray> ToJavaByteArray( + JNIEnv* env, const uint8* bytes, size_t len); + +// Returns a new Java long array converted from the given int64 array. +BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray( + JNIEnv* env, const int64* longs, size_t len); + +BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray( + JNIEnv* env, const std::vector<int64>& longs); + +// Returns a array of Java byte array converted from |v|. +BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray( + JNIEnv* env, const std::vector<std::string>& v); + +BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<std::string>& v); + +BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings( + JNIEnv* env, const std::vector<string16>& v); + +// Converts a Java string array to a native array. +BASE_EXPORT void AppendJavaStringArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<string16>* out); + +BASE_EXPORT void AppendJavaStringArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out); + +// Appends the Java bytes in |bytes_array| onto the end of |out|. +BASE_EXPORT void AppendJavaByteArrayToByteVector( + JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8>* out); + +// Replaces the content of |out| with the Java bytes in |bytes_array|. +BASE_EXPORT void JavaByteArrayToByteVector( + JNIEnv* env, + jbyteArray byte_array, + std::vector<uint8>* out); + +// Replaces the content of |out| with the Java ints in |int_array|. +BASE_EXPORT void JavaIntArrayToIntVector( + JNIEnv* env, + jintArray int_array, + std::vector<int>* out); + +// Replaces the content of |out| with the Java floats in |float_array|. +BASE_EXPORT void JavaFloatArrayToFloatVector( + JNIEnv* env, + jfloatArray float_array, + std::vector<float>* out); + +// Assuming |array| is an byte[][] (array of byte arrays), replaces the +// content of |out| with the corresponding vector of strings. No UTF-8 +// conversion is performed. +BASE_EXPORT void JavaArrayOfByteArrayToStringVector( + JNIEnv* env, + jobjectArray array, + std::vector<std::string>* out); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_ARRAY_H_ diff --git a/chromium/base/android/jni_array_unittest.cc b/chromium/base/android/jni_array_unittest.cc new file mode 100644 index 00000000000..3ac7855e402 --- /dev/null +++ b/chromium/base/android/jni_array_unittest.cc @@ -0,0 +1,148 @@ +// 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. + +#include "base/android/jni_array.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(JniArray, BasicConversions) { + const uint8 kBytes[] = { 0, 1, 2, 3 }; + const size_t kLen = arraysize(kBytes); + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jbyteArray> bytes = ToJavaByteArray(env, kBytes, kLen); + ASSERT_TRUE(bytes.obj()); + + std::vector<uint8> vec(5); + JavaByteArrayToByteVector(env, bytes.obj(), &vec); + EXPECT_EQ(4U, vec.size()); + EXPECT_EQ(std::vector<uint8>(kBytes, kBytes + kLen), vec); + + AppendJavaByteArrayToByteVector(env, bytes.obj(), &vec); + EXPECT_EQ(8U, vec.size()); +} + +void CheckLongConversion( + JNIEnv* env, + const int64* long_array, + const size_t len, + const ScopedJavaLocalRef<jlongArray>& longs) { + ASSERT_TRUE(longs.obj()); + + jsize java_array_len = env->GetArrayLength(longs.obj()); + ASSERT_EQ(static_cast<jsize>(len), java_array_len); + + jlong value; + for (size_t i = 0; i < len; ++i) { + env->GetLongArrayRegion(longs.obj(), i, 1, &value); + ASSERT_EQ(long_array[i], value); + } +} + +TEST(JniArray, LongConversions) { + const int64 kLongs[] = { 0, 1, -1, kint64min, kint64max}; + const size_t kLen = arraysize(kLongs); + + JNIEnv* env = AttachCurrentThread(); + CheckLongConversion(env, kLongs, kLen, ToJavaLongArray(env, kLongs, kLen)); + + const std::vector<int64> vec(kLongs, kLongs + kLen); + CheckLongConversion(env, kLongs, kLen, ToJavaLongArray(env, vec)); +} + +TEST(JniArray, JavaIntArrayToIntVector) { + const int kInts[] = {0, 1, -1}; + const size_t kLen = arraysize(kInts); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jintArray> jints(env, env->NewIntArray(kLen)); + ASSERT_TRUE(jints.obj()); + + for (size_t i = 0; i < kLen; ++i) { + jint j = static_cast<jint>(kInts[i]); + env->SetIntArrayRegion(jints.obj(), i, 1, &j); + ASSERT_FALSE(HasException(env)); + } + + std::vector<int> ints; + JavaIntArrayToIntVector(env, jints.obj(), &ints); + + ASSERT_EQ(static_cast<jsize>(ints.size()), env->GetArrayLength(jints.obj())); + + jint value; + for (size_t i = 0; i < kLen; ++i) { + env->GetIntArrayRegion(jints.obj(), i, 1, &value); + ASSERT_EQ(ints[i], value); + } +} + +TEST(JniArray, JavaFloatArrayToFloatVector) { + const float kFloats[] = {0.0, 0.5, -0.5}; + const size_t kLen = arraysize(kFloats); + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jfloatArray> jfloats(env, env->NewFloatArray(kLen)); + ASSERT_TRUE(jfloats.obj()); + + for (size_t i = 0; i < kLen; ++i) { + jfloat j = static_cast<jfloat>(kFloats[i]); + env->SetFloatArrayRegion(jfloats.obj(), i, 1, &j); + ASSERT_FALSE(HasException(env)); + } + + std::vector<float> floats; + JavaFloatArrayToFloatVector(env, jfloats.obj(), &floats); + + ASSERT_EQ(static_cast<jsize>(floats.size()), + env->GetArrayLength(jfloats.obj())); + + jfloat value; + for (size_t i = 0; i < kLen; ++i) { + env->GetFloatArrayRegion(jfloats.obj(), i, 1, &value); + ASSERT_EQ(floats[i], value); + } +} + +TEST(JniArray, JavaArrayOfByteArrayToStringVector) { + const int kMaxItems = 50; + JNIEnv* env = AttachCurrentThread(); + + // Create a byte[][] object. + ScopedJavaLocalRef<jclass> byte_array_clazz(env, env->FindClass("[B")); + ASSERT_TRUE(byte_array_clazz.obj()); + + ScopedJavaLocalRef<jobjectArray> array( + env, env->NewObjectArray(kMaxItems, byte_array_clazz.obj(), NULL)); + ASSERT_TRUE(array.obj()); + + // Create kMaxItems byte buffers. + char text[16]; + for (int i = 0; i < kMaxItems; ++i) { + snprintf(text, sizeof text, "%d", i); + ScopedJavaLocalRef<jbyteArray> byte_array = ToJavaByteArray( + env, reinterpret_cast<uint8*>(text), + static_cast<size_t>(strlen(text))); + ASSERT_TRUE(byte_array.obj()); + + env->SetObjectArrayElement(array.obj(), i, byte_array.obj()); + ASSERT_FALSE(HasException(env)); + } + + // Convert to std::vector<std::string>, check the content. + std::vector<std::string> vec; + JavaArrayOfByteArrayToStringVector(env, array.obj(), &vec); + + EXPECT_EQ(static_cast<size_t>(kMaxItems), vec.size()); + for (int i = 0; i < kMaxItems; ++i) { + snprintf(text, sizeof text, "%d", i); + EXPECT_STREQ(text, vec[i].c_str()); + } +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/jni_generator/golden_sample_for_tests_jni.h b/chromium/base/android/jni_generator/golden_sample_for_tests_jni.h new file mode 100644 index 00000000000..2c5f6c00255 --- /dev/null +++ b/chromium/base/android/jni_generator/golden_sample_for_tests_jni.h @@ -0,0 +1,373 @@ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/example/jni_generator/SampleForTests + +#ifndef org_chromium_example_jni_generator_SampleForTests_JNI +#define org_chromium_example_jni_generator_SampleForTests_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kInnerStructAClassPath[] = + "org/chromium/example/jni_generator/SampleForTests$InnerStructA"; +const char kSampleForTestsClassPath[] = + "org/chromium/example/jni_generator/SampleForTests"; +const char kInnerStructBClassPath[] = + "org/chromium/example/jni_generator/SampleForTests$InnerStructB"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_InnerStructA_clazz = NULL; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_SampleForTests_clazz = NULL; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_InnerStructB_clazz = NULL; +} // namespace + +namespace base { +namespace android { + +static jint Init(JNIEnv* env, jobject obj, + jstring param); + +static jdouble GetDoubleFunction(JNIEnv* env, jobject obj); + +static jfloat GetFloatFunction(JNIEnv* env, jclass clazz); + +static void SetNonPODDatatype(JNIEnv* env, jobject obj, + jobject rect); + +static jobject GetNonPODDatatype(JNIEnv* env, jobject obj); + +// Step 2: method stubs. +static void Destroy(JNIEnv* env, jobject obj, + jint nativeCPPClass) { + DCHECK(nativeCPPClass) << "Destroy"; + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + return native->Destroy(env, obj); +} + +static jint Method(JNIEnv* env, jobject obj, + jint nativeCPPClass) { + DCHECK(nativeCPPClass) << "Method"; + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + return native->Method(env, obj); +} + +static jdouble MethodOtherP0(JNIEnv* env, jobject obj, + jint nativePtr) { + DCHECK(nativePtr) << "MethodOtherP0"; + CPPClass::InnerClass* native = + reinterpret_cast<CPPClass::InnerClass*>(nativePtr); + return native->MethodOtherP0(env, obj); +} + +static void AddStructB(JNIEnv* env, jobject obj, + jint nativeCPPClass, + jobject b) { + DCHECK(nativeCPPClass) << "AddStructB"; + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + return native->AddStructB(env, obj, b); +} + +static void IterateAndDoSomethingWithStructB(JNIEnv* env, jobject obj, + jint nativeCPPClass) { + DCHECK(nativeCPPClass) << "IterateAndDoSomethingWithStructB"; + CPPClass* native = reinterpret_cast<CPPClass*>(nativeCPPClass); + return native->IterateAndDoSomethingWithStructB(env, obj); +} + +static base::subtle::AtomicWord g_SampleForTests_javaMethod = 0; +static jint Java_SampleForTests_javaMethod(JNIEnv* env, jobject obj, jint foo, + jint bar) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_SampleForTests_clazz, + "javaMethod", + +"(" +"I" +"I" +")" +"I", + &g_SampleForTests_javaMethod); + + jint ret = + env->CallIntMethod(obj, + method_id, foo, bar); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_SampleForTests_staticJavaMethod = 0; +static jboolean Java_SampleForTests_staticJavaMethod(JNIEnv* env) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, g_SampleForTests_clazz, + "staticJavaMethod", + +"(" +")" +"Z", + &g_SampleForTests_staticJavaMethod); + + jboolean ret = + env->CallStaticBooleanMethod(g_SampleForTests_clazz, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_SampleForTests_packagePrivateJavaMethod = 0; +static void Java_SampleForTests_packagePrivateJavaMethod(JNIEnv* env, jobject + obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_SampleForTests_clazz, + "packagePrivateJavaMethod", + +"(" +")" +"V", + &g_SampleForTests_packagePrivateJavaMethod); + + env->CallVoidMethod(obj, + method_id); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_SampleForTests_methodThatThrowsException = 0; +static void Java_SampleForTests_methodThatThrowsException(JNIEnv* env, jobject + obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_SampleForTests_clazz, + "methodThatThrowsException", + +"(" +")" +"V", + &g_SampleForTests_methodThatThrowsException); + + env->CallVoidMethod(obj, + method_id); + +} + +static base::subtle::AtomicWord g_InnerStructA_create = 0; +static ScopedJavaLocalRef<jobject> Java_InnerStructA_create(JNIEnv* env, jlong + l, + jint i, + jstring s) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InnerStructA_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, g_InnerStructA_clazz, + "create", + +"(" +"J" +"I" +"Ljava/lang/String;" +")" +"Lorg/chromium/example/jni_generator/SampleForTests$InnerStructA;", + &g_InnerStructA_create); + + jobject ret = + env->CallStaticObjectMethod(g_InnerStructA_clazz, + method_id, l, i, s); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +static base::subtle::AtomicWord g_SampleForTests_addStructA = 0; +static void Java_SampleForTests_addStructA(JNIEnv* env, jobject obj, jobject a) + { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_SampleForTests_clazz, + "addStructA", + +"(" +"Lorg/chromium/example/jni_generator/SampleForTests$InnerStructA;" +")" +"V", + &g_SampleForTests_addStructA); + + env->CallVoidMethod(obj, + method_id, a); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_SampleForTests_iterateAndDoSomething = 0; +static void Java_SampleForTests_iterateAndDoSomething(JNIEnv* env, jobject obj) + { + /* Must call RegisterNativesImpl() */ + DCHECK(g_SampleForTests_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_SampleForTests_clazz, + "iterateAndDoSomething", + +"(" +")" +"V", + &g_SampleForTests_iterateAndDoSomething); + + env->CallVoidMethod(obj, + method_id); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_InnerStructB_getKey = 0; +static jlong Java_InnerStructB_getKey(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InnerStructB_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InnerStructB_clazz, + "getKey", + +"(" +")" +"J", + &g_InnerStructB_getKey); + + jlong ret = + env->CallLongMethod(obj, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InnerStructB_getValue = 0; +static ScopedJavaLocalRef<jstring> Java_InnerStructB_getValue(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InnerStructB_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InnerStructB_clazz, + "getValue", + +"(" +")" +"Ljava/lang/String;", + &g_InnerStructB_getValue); + + jstring ret = + static_cast<jstring>(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef<jstring>(env, ret); +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_InnerStructA_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kInnerStructAClassPath).obj())); + g_SampleForTests_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kSampleForTestsClassPath).obj())); + g_InnerStructB_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kInnerStructBClassPath).obj())); + static const JNINativeMethod kMethodsSampleForTests[] = { + { "nativeInit", +"(" +"Ljava/lang/String;" +")" +"I", reinterpret_cast<void*>(Init) }, + { "nativeDestroy", +"(" +"I" +")" +"V", reinterpret_cast<void*>(Destroy) }, + { "nativeGetDoubleFunction", +"(" +")" +"D", reinterpret_cast<void*>(GetDoubleFunction) }, + { "nativeGetFloatFunction", +"(" +")" +"F", reinterpret_cast<void*>(GetFloatFunction) }, + { "nativeSetNonPODDatatype", +"(" +"Landroid/graphics/Rect;" +")" +"V", reinterpret_cast<void*>(SetNonPODDatatype) }, + { "nativeGetNonPODDatatype", +"(" +")" +"Ljava/lang/Object;", reinterpret_cast<void*>(GetNonPODDatatype) }, + { "nativeMethod", +"(" +"I" +")" +"I", reinterpret_cast<void*>(Method) }, + { "nativeMethodOtherP0", +"(" +"I" +")" +"D", reinterpret_cast<void*>(MethodOtherP0) }, + { "nativeAddStructB", +"(" +"I" +"Lorg/chromium/example/jni_generator/SampleForTests$InnerStructB;" +")" +"V", reinterpret_cast<void*>(AddStructB) }, + { "nativeIterateAndDoSomethingWithStructB", +"(" +"I" +")" +"V", reinterpret_cast<void*>(IterateAndDoSomethingWithStructB) }, + }; + const int kMethodsSampleForTestsSize = arraysize(kMethodsSampleForTests); + + if (env->RegisterNatives(g_SampleForTests_clazz, + kMethodsSampleForTests, + kMethodsSampleForTestsSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} +} // namespace android +} // namespace base + +#endif // org_chromium_example_jni_generator_SampleForTests_JNI diff --git a/chromium/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java b/chromium/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java new file mode 100644 index 00000000000..71cfc72d7e6 --- /dev/null +++ b/chromium/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java @@ -0,0 +1,288 @@ +// 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 org.chromium.example.jni_generator; + +import android.graphics.Rect; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.chromium.base.AccessedByNative; +import org.chromium.base.CalledByNative; +import org.chromium.base.CalledByNativeUnchecked; +import org.chromium.base.JNINamespace; +import org.chromium.base.NativeClassQualifiedName; + + +// This class serves as a reference test for the bindings generator, and as example documentation +// for how to use the jni generator. +// The C++ counter-part is sample_for_tests.cc. +// jni_generator.gyp has a jni_generator_tests target that will: +// * Generate a header file for the JNI bindings based on this file. +// * Compile sample_for_tests.cc using the generated header file. +// * link a native executable to prove the generated header + cc file are self-contained. +// All comments are informational only, and are ignored by the jni generator. +// +// Binding C/C++ with Java is not trivial, specially when ownership and object lifetime +// semantics needs to be managed across boundaries. +// Following a few guidelines will make the code simpler and less buggy: +// +// - Never write any JNI "by hand". Rely on the bindings generator to have a thin +// layer of type-safety. +// +// - Treat the types from the other side as "opaque" as possible. Do not inspect any +// object directly, but rather, rely on well-defined getters / setters. +// +// - Minimize the surface API between the two sides, and rather than calling multiple +// functions across boundaries, call only one (and then, internally in the other side, +// call as many little functions as required). +// +// - If a Java object "owns" a native object, stash the pointer in a "int mNativeClassName". +// Note that it needs to have a "destruction path", i.e., it must eventually call a method +// to delete the native object (for example, the java object has a "close()" method that +// in turn deletes the native object). Avoid relying on finalizers: those run in a different +// thread and makes the native lifetime management more difficult. +// +// - For native object "owning" java objects: +// - If there's a strong 1:1 to relationship between native and java, the best way is to +// stash the java object into a base::android::ScopedJavaGlobalRef. This will ensure the +// java object can be GC'd once the native object is destroyed but note that this global strong +// ref implies a new GC root, so be sure it will not leak and it must never rely on being +// triggered (transitively) from a java side GC. +// - In all other cases, the native side should keep a JavaObjectWeakGlobalRef, and check whether +// that reference is still valid before de-referencing it. Note that you will need another +// java-side object to be holding a strong reference to this java object while it is in use, to +// avoid unpredictable GC of the object before native side has finished with it. +// +// - The best way to pass "compound" datatypes across in either direction is to create an inner +// class with PODs and a factory function. If possible, make it immutable (i.e., mark all the +// fields as "final"). See examples with "InnerStructB" below. +// +// - It's simpler to create thin wrappers with a well defined JNI interface than to +// expose a lot of internal details. This is specially significant for system classes where it's +// simpler to wrap factory methods and a few getters / setters than expose the entire class. +// +// - Use static factory functions annotated with @CalledByNative rather than calling the +// constructors directly. +// +// - Iterate over containers where they are originally owned, then create inner structs or +// directly call methods on the other side. It's much simpler than trying to amalgamate +// java and stl containers. +// +// This JNINamespace annotation indicates that all native methods should be +// generated inside this namespace, including the native class that this +// object binds to. +@JNINamespace("base::android") +class SampleForTests { + // Classes can store their C++ pointer counter part as an int that is normally initialized by + // calling out a nativeInit() function. + int mNativeCPPObject; + + // You can define methods and attributes on the java class just like any other. + // Methods without the @CalledByNative annotation won't be exposed to JNI. + public SampleForTests() { + } + + public void startExample() { + // Calls native code and holds a pointer to the C++ class. + mNativeCPPObject = nativeInit("myParam"); + } + + public void doStuff() { + // This will call CPPClass::Method() using nativePtr as a pointer to the object. This must be + // done to: + // * avoid leaks. + // * using finalizers are not allowed to destroy the cpp class. + nativeMethod(mNativeCPPObject); + } + + public void finishExample() { + // We're done, so let's destroy nativePtr object. + nativeDestroy(mNativeCPPObject); + } + + // ----------------------------------------------------------------------------------------------- + // The following methods demonstrate exporting Java methods for invocation from C++ code. + // Java functions are mapping into C global functions by prefixing the method name with + // "Java_<Class>_" + // This is triggered by the @CalledByNative annotation; the methods may be named as you wish. + + // Exported to C++ as: + // Java_Example_javaMethod(JNIEnv* env, jobject obj, jint foo, jint bar) + // Typically the C++ code would have obtained the jobject via the Init() call described above. + @CalledByNative + public int javaMethod(int foo, + int bar) { + return 0; + } + + // Exported to C++ as Java_Example_staticJavaMethod(JNIEnv* env) + // Note no jobject argument, as it is static. + @CalledByNative + public static boolean staticJavaMethod() { + return true; + } + + // No prefix, so this method is package private. It will still be exported. + @CalledByNative + void packagePrivateJavaMethod() {} + + // Note the "Unchecked" suffix. By default, @CalledByNative will always generate bindings that + // call CheckException(). With "@CalledByNativeUnchecked", the client C++ code is responsible to + // call ClearException() and act as appropriate. + // See more details at the "@CalledByNativeUnchecked" annotation. + @CalledByNativeUnchecked + void methodThatThrowsException() throws Exception {} + + // The generator is not confused by inline comments: + // @CalledByNative void thisShouldNotAppearInTheOutput(); + // @CalledByNativeUnchecked public static void neitherShouldThis(int foo); + + /** + * The generator is not confused by block comments: + * @CalledByNative void thisShouldNotAppearInTheOutputEither(); + * @CalledByNativeUnchecked public static void andDefinitelyNotThis(int foo); + */ + + // String constants that look like comments don't confuse the generator: + private String arrgh = "*/*"; + + //------------------------------------------------------------------------------------------------ + // Java fields which are accessed from C++ code only must be annotated with @AccessedByNative to + // prevent them being eliminated when unreferenced code is stripped. + @AccessedByNative + private int javaField; + + //------------------------------------------------------------------------------------------------ + // The following methods demonstrate declaring methods to call into C++ from Java. + // The generator detects the "native" and "static" keywords, the type and name of the first + // parameter, and the "native" prefix to the function name to determine the C++ function + // signatures. Besides these constraints the methods can be freely named. + + // This declares a C++ function which the application code must implement: + // static jint Init(JNIEnv* env, jobject obj); + // The jobject parameter refers back to this java side object instance. + // The implementation must return the pointer to the C++ object cast to jint. + // The caller of this method should store it, and supply it as a the nativeCPPClass param to + // subsequent native method calls (see the methods below that take an "int native..." as first + // param). + private native int nativeInit(String param); + + // This defines a function binding to the associated C++ class member function. The name is + // derived from |nativeDestroy| and |nativeCPPClass| to arrive at CPPClass::Destroy() (i.e. native + // prefixes stripped). + // The |nativeCPPClass| is automatically cast to type CPPClass* in order to obtain the object on + // which to invoke the member function. + private native void nativeDestroy(int nativeCPPClass); + + // This declares a C++ function which the application code must implement: + // static jdouble GetDoubleFunction(JNIEnv* env, jobject obj); + // The jobject parameter refers back to this java side object instance. + private native double nativeGetDoubleFunction(); + + // Similar to nativeGetDoubleFunction(), but here the C++ side will receive a jclass rather than + // jobject param, as the function is declared static. + private static native float nativeGetFloatFunction(); + + // This function takes a non-POD datatype. We have a list mapping them to their full classpath in + // jni_generator.py JavaParamToJni. If you require a new datatype, make sure you add to that + // function. + private native void nativeSetNonPODDatatype(Rect rect); + + // This declares a C++ function which the application code must implement: + // static ScopedJavaLocalRef<jobject> GetNonPODDatatype(JNIEnv* env, jobject obj); + // The jobject parameter refers back to this java side object instance. + // Note that it returns a ScopedJavaLocalRef<jobject> so that you don' have to worry about + // deleting the JNI local reference. This is similar with Strings and arrays. + private native Object nativeGetNonPODDatatype(); + + // Similar to nativeDestroy above, this will cast nativeCPPClass into pointer of CPPClass type and + // call its Method member function. + private native int nativeMethod(int nativeCPPClass); + + // Similar to nativeMethod above, but here the C++ fully qualified class name is taken from the + // annotation rather than parameter name, which can thus be chosen freely. + @NativeClassQualifiedName("CPPClass::InnerClass") + private native double nativeMethodOtherP0(int nativePtr); + + // This "struct" will be created by the native side using |createInnerStructA|, + // and used by the java-side somehow. + // Note that |@CalledByNative| has to contain the inner class name. + static class InnerStructA { + private final long mLong; + private final int mInt; + private final String mString; + + private InnerStructA(long l, int i, String s) { + mLong = l; + mInt = i; + mString = s; + } + + @CalledByNative("InnerStructA") + private static InnerStructA create(long l, int i, String s) { + return new InnerStructA(l, i, s); + } + } + + private List<InnerStructA> mListInnerStructA = new ArrayList<InnerStructA>(); + + @CalledByNative + private void addStructA(InnerStructA a) { + // Called by the native side to append another element. + mListInnerStructA.add(a); + } + + @CalledByNative + private void iterateAndDoSomething() { + Iterator<InnerStructA> it = mListInnerStructA.iterator(); + while (it.hasNext()) { + InnerStructA element = it.next(); + // Now, do something with element. + } + // Done, clear the list. + mListInnerStructA.clear(); + } + + // This "struct" will be created by the java side passed to native, which + // will use its getters. + // Note that |@CalledByNative| has to contain the inner class name. + static class InnerStructB { + private final long mKey; + private final String mValue; + + private InnerStructB(long k, String v) { + mKey = k; + mValue = v; + } + + @CalledByNative("InnerStructB") + private long getKey() { + return mKey; + } + + @CalledByNative("InnerStructB") + private String getValue() { + return mValue; + } + } + + List<InnerStructB> mListInnerStructB = new ArrayList<InnerStructB>(); + + void iterateAndDoSomethingWithMap() { + Iterator<InnerStructB> it = mListInnerStructB.iterator(); + while (it.hasNext()) { + InnerStructB element = it.next(); + // Now, do something with element. + nativeAddStructB(mNativeCPPObject, element); + } + nativeIterateAndDoSomethingWithStructB(mNativeCPPObject); + } + + native void nativeAddStructB(int nativeCPPClass, InnerStructB b); + native void nativeIterateAndDoSomethingWithStructB(int nativeCPPClass); +} diff --git a/chromium/base/android/jni_generator/jni_generator.gyp b/chromium/base/android/jni_generator/jni_generator.gyp new file mode 100644 index 00000000000..4bad0dc4832 --- /dev/null +++ b/chromium/base/android/jni_generator/jni_generator.gyp @@ -0,0 +1,67 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'jni_generator_py_tests', + 'type': 'none', + 'actions': [ + { + 'action_name': 'run_jni_generator_py_tests', + 'inputs': [ + 'jni_generator.py', + 'jni_generator_tests.py', + 'java/src/org/chromium/example/jni_generator/SampleForTests.java', + 'golden_sample_for_tests_jni.h', + ], + 'outputs': [ + '', + ], + 'action': [ + 'python', 'jni_generator_tests.py', + ], + }, + ], + }, + { + 'target_name': 'jni_sample_header', + 'type': 'none', + 'sources': [ + 'java/src/org/chromium/example/jni_generator/SampleForTests.java', + ], + 'variables': { + 'jni_gen_package': 'example', + }, + 'includes': [ '../../../build/jni_generator.gypi' ], + }, + { + 'target_name': 'jni_sample_java', + 'type': 'none', + 'variables': { + 'java_in_dir': '../../../base/android/jni_generator/java', + }, + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base_java', + ], + 'includes': [ '../../../build/java.gypi' ], + }, + { + 'target_name': 'jni_generator_tests', + 'type': 'executable', + 'dependencies': [ + '../../base.gyp:test_support_base', + 'jni_generator_py_tests', + 'jni_sample_header', + 'jni_sample_java', + ], + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/example', + ], + 'sources': [ + 'sample_for_tests.cc', + ], + }, + ], +} diff --git a/chromium/base/android/jni_generator/jni_generator.py b/chromium/base/android/jni_generator/jni_generator.py new file mode 100755 index 00000000000..de865d527be --- /dev/null +++ b/chromium/base/android/jni_generator/jni_generator.py @@ -0,0 +1,1065 @@ +#!/usr/bin/env python +# 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. + +"""Extracts native methods from a Java file and generates the JNI bindings. +If you change this, please run and update the tests.""" + +import collections +import errno +import optparse +import os +import re +import string +from string import Template +import subprocess +import sys +import textwrap +import zipfile + + +class ParseError(Exception): + """Exception thrown when we can't parse the input file.""" + + def __init__(self, description, *context_lines): + Exception.__init__(self) + self.description = description + self.context_lines = context_lines + + def __str__(self): + context = '\n'.join(self.context_lines) + return '***\nERROR: %s\n\n%s\n***' % (self.description, context) + + +class Param(object): + """Describes a param for a method, either java or native.""" + + def __init__(self, **kwargs): + self.datatype = kwargs['datatype'] + self.name = kwargs['name'] + + +class NativeMethod(object): + """Describes a C/C++ method that is called by Java code""" + + def __init__(self, **kwargs): + self.static = kwargs['static'] + self.java_class_name = kwargs['java_class_name'] + self.return_type = kwargs['return_type'] + self.name = kwargs['name'] + self.params = kwargs['params'] + if self.params: + assert type(self.params) is list + assert type(self.params[0]) is Param + if (self.params and + self.params[0].datatype == 'int' and + self.params[0].name.startswith('native')): + self.type = 'method' + self.p0_type = self.params[0].name[len('native'):] + if kwargs.get('native_class_name'): + self.p0_type = kwargs['native_class_name'] + else: + self.type = 'function' + self.method_id_var_name = kwargs.get('method_id_var_name', None) + + +class CalledByNative(object): + """Describes a java method exported to c/c++""" + + def __init__(self, **kwargs): + self.system_class = kwargs['system_class'] + self.unchecked = kwargs['unchecked'] + self.static = kwargs['static'] + self.java_class_name = kwargs['java_class_name'] + self.return_type = kwargs['return_type'] + self.name = kwargs['name'] + self.params = kwargs['params'] + self.method_id_var_name = kwargs.get('method_id_var_name', None) + self.is_constructor = kwargs.get('is_constructor', False) + self.env_call = GetEnvCall(self.is_constructor, self.static, + self.return_type) + self.static_cast = GetStaticCastForReturnType(self.return_type) + + +def JavaDataTypeToC(java_type): + """Returns a C datatype for the given java type.""" + java_pod_type_map = { + 'int': 'jint', + 'byte': 'jbyte', + 'char': 'jchar', + 'short': 'jshort', + 'boolean': 'jboolean', + 'long': 'jlong', + 'double': 'jdouble', + 'float': 'jfloat', + } + java_type_map = { + 'void': 'void', + 'String': 'jstring', + 'java/lang/String': 'jstring', + 'Class': 'jclass', + 'java/lang/Class': 'jclass', + } + + if java_type in java_pod_type_map: + return java_pod_type_map[java_type] + elif java_type in java_type_map: + return java_type_map[java_type] + elif java_type.endswith('[]'): + if java_type[:-2] in java_pod_type_map: + return java_pod_type_map[java_type[:-2]] + 'Array' + return 'jobjectArray' + else: + return 'jobject' + + +class JniParams(object): + _imports = [] + _fully_qualified_class = '' + _package = '' + _inner_classes = [] + _remappings = [] + + @staticmethod + def SetFullyQualifiedClass(fully_qualified_class): + JniParams._fully_qualified_class = 'L' + fully_qualified_class + JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1]) + + @staticmethod + def ExtractImportsAndInnerClasses(contents): + contents = contents.replace('\n', '') + re_import = re.compile(r'import.*?(?P<class>\S*?);') + for match in re.finditer(re_import, contents): + JniParams._imports += ['L' + match.group('class').replace('.', '/')] + + re_inner = re.compile(r'(class|interface)\s+?(?P<name>\w+?)\W') + for match in re.finditer(re_inner, contents): + inner = match.group('name') + if not JniParams._fully_qualified_class.endswith(inner): + JniParams._inner_classes += [JniParams._fully_qualified_class + '$' + + inner] + + @staticmethod + def JavaToJni(param): + """Converts a java param into a JNI signature type.""" + pod_param_map = { + 'int': 'I', + 'boolean': 'Z', + 'char': 'C', + 'short': 'S', + 'long': 'J', + 'double': 'D', + 'float': 'F', + 'byte': 'B', + 'void': 'V', + } + object_param_list = [ + 'Ljava/lang/Boolean', + 'Ljava/lang/Integer', + 'Ljava/lang/Long', + 'Ljava/lang/Object', + 'Ljava/lang/String', + 'Ljava/lang/Class', + ] + prefix = '' + # Array? + while param[-2:] == '[]': + prefix += '[' + param = param[:-2] + # Generic? + if '<' in param: + param = param[:param.index('<')] + if param in pod_param_map: + return prefix + pod_param_map[param] + if '/' in param: + # Coming from javap, use the fully qualified param directly. + return prefix + 'L' + JniParams.RemapClassName(param) + ';' + for qualified_name in (object_param_list + + [JniParams._fully_qualified_class] + + JniParams._inner_classes): + if (qualified_name.endswith('/' + param) or + qualified_name.endswith('$' + param.replace('.', '$')) or + qualified_name == 'L' + param): + return prefix + JniParams.RemapClassName(qualified_name) + ';' + + # Is it from an import? (e.g. referecing Class from import pkg.Class; + # note that referencing an inner class Inner from import pkg.Class.Inner + # is not supported). + for qualified_name in JniParams._imports: + if qualified_name.endswith('/' + param): + # Ensure it's not an inner class. + components = qualified_name.split('/') + if len(components) > 2 and components[-2][0].isupper(): + raise SyntaxError('Inner class (%s) can not be imported ' + 'and used by JNI (%s). Please import the outer ' + 'class and use Outer.Inner instead.' % + (qualified_name, param)) + return prefix + JniParams.RemapClassName(qualified_name) + ';' + + # Is it an inner class from an outer class import? (e.g. referencing + # Class.Inner from import pkg.Class). + if '.' in param: + components = param.split('.') + outer = '/'.join(components[:-1]) + inner = components[-1] + for qualified_name in JniParams._imports: + if qualified_name.endswith('/' + outer): + return (prefix + JniParams.RemapClassName(qualified_name) + + '$' + inner + ';') + + # Type not found, falling back to same package as this class. + return (prefix + 'L' + + JniParams.RemapClassName(JniParams._package + '/' + param) + ';') + + @staticmethod + def Signature(params, returns, wrap): + """Returns the JNI signature for the given datatypes.""" + items = ['('] + items += [JniParams.JavaToJni(param.datatype) for param in params] + items += [')'] + items += [JniParams.JavaToJni(returns)] + if wrap: + return '\n' + '\n'.join(['"' + item + '"' for item in items]) + else: + return '"' + ''.join(items) + '"' + + @staticmethod + def Parse(params): + """Parses the params into a list of Param objects.""" + if not params: + return [] + ret = [] + for p in [p.strip() for p in params.split(',')]: + items = p.split(' ') + if 'final' in items: + items.remove('final') + param = Param( + datatype=items[0], + name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), + ) + ret += [param] + return ret + + @staticmethod + def RemapClassName(class_name): + """Remaps class names using the jarjar mapping table.""" + for old, new in JniParams._remappings: + if old in class_name: + return class_name.replace(old, new, 1) + return class_name + + @staticmethod + def SetJarJarMappings(mappings): + """Parse jarjar mappings from a string.""" + JniParams._remappings = [] + for line in mappings.splitlines(): + keyword, src, dest = line.split() + if keyword != 'rule': + continue + assert src.endswith('.**') + src = src[:-2].replace('.', '/') + dest = dest.replace('.', '/') + if dest.endswith('@0'): + JniParams._remappings.append((src, dest[:-2] + src)) + else: + assert dest.endswith('@1') + JniParams._remappings.append((src, dest[:-2])) + + +def ExtractJNINamespace(contents): + re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)') + m = re.findall(re_jni_namespace, contents) + if not m: + return '' + return m[0] + + +def ExtractFullyQualifiedJavaClassName(java_file_name, contents): + re_package = re.compile('.*?package (.*?);') + matches = re.findall(re_package, contents) + if not matches: + raise SyntaxError('Unable to find "package" line in %s' % java_file_name) + return (matches[0].replace('.', '/') + '/' + + os.path.splitext(os.path.basename(java_file_name))[0]) + + +def ExtractNatives(contents): + """Returns a list of dict containing information about a native method.""" + contents = contents.replace('\n', '') + natives = [] + re_native = re.compile(r'(@NativeClassQualifiedName' + '\(\"(?P<native_class_name>.*?)\"\))?\s*' + '(@NativeCall(\(\"(?P<java_class_name>.*?)\"\)))?\s*' + '(?P<qualifiers>\w+\s\w+|\w+|\s+)\s*?native ' + '(?P<return_type>\S*?) ' + '(?P<name>\w+?)\((?P<params>.*?)\);') + for match in re.finditer(re_native, contents): + native = NativeMethod( + static='static' in match.group('qualifiers'), + java_class_name=match.group('java_class_name'), + native_class_name=match.group('native_class_name'), + return_type=match.group('return_type'), + name=match.group('name').replace('native', ''), + params=JniParams.Parse(match.group('params'))) + natives += [native] + return natives + + +def GetStaticCastForReturnType(return_type): + type_map = { 'String' : 'jstring', + 'java/lang/String' : 'jstring', + 'boolean[]': 'jbooleanArray', + 'byte[]': 'jbyteArray', + 'char[]': 'jcharArray', + 'short[]': 'jshortArray', + 'int[]': 'jintArray', + 'long[]': 'jlongArray', + 'double[]': 'jdoubleArray' } + ret = type_map.get(return_type, None) + if ret: + return ret + if return_type.endswith('[]'): + return 'jobjectArray' + return None + + +def GetEnvCall(is_constructor, is_static, return_type): + """Maps the types availabe via env->Call__Method.""" + if is_constructor: + return 'NewObject' + env_call_map = {'boolean': 'Boolean', + 'byte': 'Byte', + 'char': 'Char', + 'short': 'Short', + 'int': 'Int', + 'long': 'Long', + 'float': 'Float', + 'void': 'Void', + 'double': 'Double', + 'Object': 'Object', + } + call = env_call_map.get(return_type, 'Object') + if is_static: + call = 'Static' + call + return 'Call' + call + 'Method' + + +def GetMangledParam(datatype): + """Returns a mangled identifier for the datatype.""" + if len(datatype) <= 2: + return datatype.replace('[', 'A') + ret = '' + for i in range(1, len(datatype)): + c = datatype[i] + if c == '[': + ret += 'A' + elif c.isupper() or datatype[i - 1] in ['/', 'L']: + ret += c.upper() + return ret + + +def GetMangledMethodName(name, params, return_type): + """Returns a mangled method name for the given signature. + + The returned name can be used as a C identifier and will be unique for all + valid overloads of the same method. + + Args: + name: string. + params: list of Param. + return_type: string. + + Returns: + A mangled name. + """ + mangled_items = [] + for datatype in [return_type] + [x.datatype for x in params]: + mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))] + mangled_name = name + '_'.join(mangled_items) + assert re.match(r'[0-9a-zA-Z_]+', mangled_name) + return mangled_name + + +def MangleCalledByNatives(called_by_natives): + """Mangles all the overloads from the call_by_natives list.""" + method_counts = collections.defaultdict( + lambda: collections.defaultdict(lambda: 0)) + for called_by_native in called_by_natives: + java_class_name = called_by_native.java_class_name + name = called_by_native.name + method_counts[java_class_name][name] += 1 + for called_by_native in called_by_natives: + java_class_name = called_by_native.java_class_name + method_name = called_by_native.name + method_id_var_name = method_name + if method_counts[java_class_name][method_name] > 1: + method_id_var_name = GetMangledMethodName(method_name, + called_by_native.params, + called_by_native.return_type) + called_by_native.method_id_var_name = method_id_var_name + return called_by_natives + + +# Regex to match the JNI return types that should be included in a +# ScopedJavaLocalRef. +RE_SCOPED_JNI_RETURN_TYPES = re.compile('jobject|jclass|jstring|.*Array') + +# Regex to match a string like "@CalledByNative public void foo(int bar)". +RE_CALLED_BY_NATIVE = re.compile( + '@CalledByNative(?P<Unchecked>(Unchecked)*?)(?:\("(?P<annotation>.*)"\))?' + '\s+(?P<prefix>[\w ]*?)' + '\s*(?P<return_type>\S+?)' + '\s+(?P<name>\w+)' + '\s*\((?P<params>[^\)]*)\)') + + +def ExtractCalledByNatives(contents): + """Parses all methods annotated with @CalledByNative. + + Args: + contents: the contents of the java file. + + Returns: + A list of dict with information about the annotated methods. + TODO(bulach): return a CalledByNative object. + + Raises: + ParseError: if unable to parse. + """ + called_by_natives = [] + for match in re.finditer(RE_CALLED_BY_NATIVE, contents): + called_by_natives += [CalledByNative( + system_class=False, + unchecked='Unchecked' in match.group('Unchecked'), + static='static' in match.group('prefix'), + java_class_name=match.group('annotation') or '', + return_type=match.group('return_type'), + name=match.group('name'), + params=JniParams.Parse(match.group('params')))] + # Check for any @CalledByNative occurrences that weren't matched. + unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n') + for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): + if '@CalledByNative' in line1: + raise ParseError('could not parse @CalledByNative method signature', + line1, line2) + return MangleCalledByNatives(called_by_natives) + + +class JNIFromJavaP(object): + """Uses 'javap' to parse a .class file and generate the JNI header file.""" + + def __init__(self, contents, namespace): + self.contents = contents + self.namespace = namespace + self.fully_qualified_class = re.match( + '.*?(class|interface) (?P<class_name>.*?)( |{)', + contents[1]).group('class_name') + self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') + JniParams.SetFullyQualifiedClass(self.fully_qualified_class) + self.java_class_name = self.fully_qualified_class.split('/')[-1] + if not self.namespace: + self.namespace = 'JNI_' + self.java_class_name + re_method = re.compile('(?P<prefix>.*?)(?P<return_type>\S+?) (?P<name>\w+?)' + '\((?P<params>.*?)\)') + self.called_by_natives = [] + for content in contents[2:]: + match = re.match(re_method, content) + if not match: + continue + self.called_by_natives += [CalledByNative( + system_class=True, + unchecked=False, + static='static' in match.group('prefix'), + java_class_name='', + return_type=match.group('return_type').replace('.', '/'), + name=match.group('name'), + params=JniParams.Parse(match.group('params').replace('.', '/')))] + re_constructor = re.compile('.*? public ' + + self.fully_qualified_class.replace('/', '.') + + '\((?P<params>.*?)\)') + for content in contents[2:]: + match = re.match(re_constructor, content) + if not match: + continue + self.called_by_natives += [CalledByNative( + system_class=True, + unchecked=False, + static=False, + java_class_name='', + return_type=self.fully_qualified_class, + name='Constructor', + params=JniParams.Parse(match.group('params').replace('.', '/')), + is_constructor=True)] + self.called_by_natives = MangleCalledByNatives(self.called_by_natives) + self.inl_header_file_generator = InlHeaderFileGenerator( + self.namespace, self.fully_qualified_class, [], self.called_by_natives) + + def GetContent(self): + return self.inl_header_file_generator.GetContent() + + @staticmethod + def CreateFromClass(class_file, namespace): + class_name = os.path.splitext(os.path.basename(class_file))[0] + p = subprocess.Popen(args=['javap', class_name], + cwd=os.path.dirname(class_file), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, _ = p.communicate() + jni_from_javap = JNIFromJavaP(stdout.split('\n'), namespace) + return jni_from_javap + + +class JNIFromJavaSource(object): + """Uses the given java source file to generate the JNI header file.""" + + def __init__(self, contents, fully_qualified_class): + contents = self._RemoveComments(contents) + JniParams.SetFullyQualifiedClass(fully_qualified_class) + JniParams.ExtractImportsAndInnerClasses(contents) + jni_namespace = ExtractJNINamespace(contents) + natives = ExtractNatives(contents) + called_by_natives = ExtractCalledByNatives(contents) + if len(natives) == 0 and len(called_by_natives) == 0: + raise SyntaxError('Unable to find any JNI methods for %s.' % + fully_qualified_class) + inl_header_file_generator = InlHeaderFileGenerator( + jni_namespace, fully_qualified_class, natives, called_by_natives) + self.content = inl_header_file_generator.GetContent() + + def _RemoveComments(self, contents): + # We need to support both inline and block comments, and we need to handle + # strings that contain '//' or '/*'. Rather than trying to do all that with + # regexps, we just pipe the contents through the C preprocessor. We tell cpp + # the file has already been preprocessed, so it just removes comments and + # doesn't try to parse #include, #pragma etc. + # + # TODO(husky): This is a bit hacky. It would be cleaner to use a real Java + # parser. Maybe we could ditch JNIFromJavaSource and just always use + # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT. + # http://code.google.com/p/chromium/issues/detail?id=138941 + p = subprocess.Popen(args=['cpp', '-fpreprocessed'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, _ = p.communicate(contents) + return stdout + + def GetContent(self): + return self.content + + @staticmethod + def CreateFromFile(java_file_name): + contents = file(java_file_name).read() + fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name, + contents) + return JNIFromJavaSource(contents, fully_qualified_class) + + +class InlHeaderFileGenerator(object): + """Generates an inline header file for JNI integration.""" + + def __init__(self, namespace, fully_qualified_class, natives, + called_by_natives): + self.namespace = namespace + self.fully_qualified_class = fully_qualified_class + self.class_name = self.fully_qualified_class.split('/')[-1] + self.natives = natives + self.called_by_natives = called_by_natives + self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' + + def GetContent(self): + """Returns the content of the JNI binding file.""" + template = Template("""\ +// 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. + + +// This file is autogenerated by +// ${SCRIPT_NAME} +// For +// ${FULLY_QUALIFIED_CLASS} + +#ifndef ${HEADER_GUARD} +#define ${HEADER_GUARD} + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +$CLASS_PATH_DEFINITIONS +} // namespace + +$OPEN_NAMESPACE +$FORWARD_DECLARATIONS + +// Step 2: method stubs. +$METHOD_STUBS + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { +$REGISTER_NATIVES_IMPL + return true; +} +$CLOSE_NAMESPACE +#endif // ${HEADER_GUARD} +""") + script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) + base_index = script_components.index('base') + script_name = os.sep.join(script_components[base_index:]) + values = { + 'SCRIPT_NAME': script_name, + 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, + 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), + 'FORWARD_DECLARATIONS': self.GetForwardDeclarationsString(), + 'METHOD_STUBS': self.GetMethodStubsString(), + 'OPEN_NAMESPACE': self.GetOpenNamespaceString(), + 'REGISTER_NATIVES_IMPL': self.GetRegisterNativesImplString(), + 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(), + 'HEADER_GUARD': self.header_guard, + } + return WrapOutput(template.substitute(values)) + + def GetClassPathDefinitionsString(self): + ret = [] + ret += [self.GetClassPathDefinitions()] + return '\n'.join(ret) + + def GetForwardDeclarationsString(self): + ret = [] + for native in self.natives: + if native.type != 'method': + ret += [self.GetForwardDeclaration(native)] + return '\n'.join(ret) + + def GetMethodStubsString(self): + ret = [] + for native in self.natives: + if native.type == 'method': + ret += [self.GetNativeMethodStub(native)] + for called_by_native in self.called_by_natives: + ret += [self.GetCalledByNativeMethodStub(called_by_native)] + return '\n'.join(ret) + + def GetKMethodsString(self, clazz): + ret = [] + for native in self.natives: + if (native.java_class_name == clazz or + (not native.java_class_name and clazz == self.class_name)): + ret += [self.GetKMethodArrayEntry(native)] + return '\n'.join(ret) + + def GetRegisterNativesImplString(self): + """Returns the implementation for RegisterNatives.""" + template = Template("""\ + static const JNINativeMethod kMethods${JAVA_CLASS}[] = { +${KMETHODS} + }; + const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS}); + + if (env->RegisterNatives(g_${JAVA_CLASS}_clazz, + kMethods${JAVA_CLASS}, + kMethods${JAVA_CLASS}Size) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } +""") + ret = [self.GetFindClasses()] + all_classes = self.GetUniqueClasses(self.natives) + all_classes[self.class_name] = self.fully_qualified_class + for clazz in all_classes: + kmethods = self.GetKMethodsString(clazz) + if kmethods: + values = {'JAVA_CLASS': clazz, + 'KMETHODS': kmethods} + ret += [template.substitute(values)] + if not ret: return '' + return '\n' + '\n'.join(ret) + + def GetOpenNamespaceString(self): + if self.namespace: + all_namespaces = ['namespace %s {' % ns + for ns in self.namespace.split('::')] + return '\n'.join(all_namespaces) + return '' + + def GetCloseNamespaceString(self): + if self.namespace: + all_namespaces = ['} // namespace %s' % ns + for ns in self.namespace.split('::')] + all_namespaces.reverse() + return '\n'.join(all_namespaces) + '\n' + return '' + + def GetJNIFirstParam(self, native): + ret = [] + if native.type == 'method': + ret = ['jobject obj'] + elif native.type == 'function': + if native.static: + ret = ['jclass clazz'] + else: + ret = ['jobject obj'] + return ret + + def GetParamsInDeclaration(self, native): + """Returns the params for the stub declaration. + + Args: + native: the native dictionary describing the method. + + Returns: + A string containing the params. + """ + return ',\n '.join(self.GetJNIFirstParam(native) + + [JavaDataTypeToC(param.datatype) + ' ' + + param.name + for param in native.params]) + + def GetCalledByNativeParamsInDeclaration(self, called_by_native): + return ',\n '.join([JavaDataTypeToC(param.datatype) + ' ' + + param.name + for param in called_by_native.params]) + + def GetForwardDeclaration(self, native): + template = Template(""" +static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS}); +""") + values = {'RETURN': JavaDataTypeToC(native.return_type), + 'NAME': native.name, + 'PARAMS': self.GetParamsInDeclaration(native)} + return template.substitute(values) + + def GetNativeMethodStub(self, native): + """Returns stubs for native methods.""" + template = Template("""\ +static ${RETURN} ${NAME}(JNIEnv* env, ${PARAMS_IN_DECLARATION}) { + DCHECK(${PARAM0_NAME}) << "${NAME}"; + ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME}); + return native->${NAME}(env, obj${PARAMS_IN_CALL})${POST_CALL}; +} +""") + params_for_call = ', '.join(p.name for p in native.params[1:]) + if params_for_call: + params_for_call = ', ' + params_for_call + + return_type = JavaDataTypeToC(native.return_type) + if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type): + scoped_return_type = 'ScopedJavaLocalRef<' + return_type + '>' + post_call = '.Release()' + else: + scoped_return_type = return_type + post_call = '' + values = { + 'RETURN': return_type, + 'SCOPED_RETURN': scoped_return_type, + 'NAME': native.name, + 'PARAMS_IN_DECLARATION': self.GetParamsInDeclaration(native), + 'PARAM0_NAME': native.params[0].name, + 'P0_TYPE': native.p0_type, + 'PARAMS_IN_CALL': params_for_call, + 'POST_CALL': post_call + } + return template.substitute(values) + + def GetCalledByNativeMethodStub(self, called_by_native): + """Returns a string.""" + function_signature_template = Template("""\ +static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\ +JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""") + function_header_template = Template("""\ +${FUNCTION_SIGNATURE} {""") + function_header_with_unused_template = Template("""\ +${FUNCTION_SIGNATURE} __attribute__ ((unused)); +${FUNCTION_SIGNATURE} {""") + template = Template(""" +static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0; +${FUNCTION_HEADER} + /* Must call RegisterNativesImpl() */ + DCHECK(g_${JAVA_CLASS}_clazz); + jmethodID method_id = + ${GET_METHOD_ID_IMPL} + ${RETURN_DECLARATION} + ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL}, + method_id${PARAMS_IN_CALL})${POST_CALL}; + ${CHECK_EXCEPTION} + ${RETURN_CLAUSE} +}""") + if called_by_native.static or called_by_native.is_constructor: + first_param_in_declaration = '' + first_param_in_call = ('g_%s_clazz' % + (called_by_native.java_class_name or + self.class_name)) + else: + first_param_in_declaration = ', jobject obj' + first_param_in_call = 'obj' + params_in_declaration = self.GetCalledByNativeParamsInDeclaration( + called_by_native) + if params_in_declaration: + params_in_declaration = ', ' + params_in_declaration + params_for_call = ', '.join(param.name + for param in called_by_native.params) + if params_for_call: + params_for_call = ', ' + params_for_call + pre_call = '' + post_call = '' + if called_by_native.static_cast: + pre_call = 'static_cast<%s>(' % called_by_native.static_cast + post_call = ')' + check_exception = '' + if not called_by_native.unchecked: + check_exception = 'base::android::CheckException(env);' + return_type = JavaDataTypeToC(called_by_native.return_type) + return_declaration = '' + return_clause = '' + if return_type != 'void': + pre_call = ' ' + pre_call + return_declaration = return_type + ' ret =' + if re.match(RE_SCOPED_JNI_RETURN_TYPES, return_type): + return_type = 'ScopedJavaLocalRef<' + return_type + '>' + return_clause = 'return ' + return_type + '(env, ret);' + else: + return_clause = 'return ret;' + values = { + 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, + 'METHOD': called_by_native.name, + 'RETURN_TYPE': return_type, + 'RETURN_DECLARATION': return_declaration, + 'RETURN_CLAUSE': return_clause, + 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration, + 'PARAMS_IN_DECLARATION': params_in_declaration, + 'STATIC': 'Static' if called_by_native.static else '', + 'PRE_CALL': pre_call, + 'POST_CALL': post_call, + 'ENV_CALL': called_by_native.env_call, + 'FIRST_PARAM_IN_CALL': first_param_in_call, + 'PARAMS_IN_CALL': params_for_call, + 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, + 'CHECK_EXCEPTION': check_exception, + 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native) + } + values['FUNCTION_SIGNATURE'] = ( + function_signature_template.substitute(values)) + if called_by_native.system_class: + values['FUNCTION_HEADER'] = ( + function_header_with_unused_template.substitute(values)) + else: + values['FUNCTION_HEADER'] = function_header_template.substitute(values) + return template.substitute(values) + + def GetKMethodArrayEntry(self, native): + template = Template("""\ + { "native${NAME}", ${JNI_SIGNATURE}, reinterpret_cast<void*>(${NAME}) },""") + values = {'NAME': native.name, + 'JNI_SIGNATURE': JniParams.Signature(native.params, + native.return_type, + True)} + return template.substitute(values) + + def GetUniqueClasses(self, origin): + ret = {self.class_name: self.fully_qualified_class} + for entry in origin: + class_name = self.class_name + jni_class_path = self.fully_qualified_class + if entry.java_class_name: + class_name = entry.java_class_name + jni_class_path = self.fully_qualified_class + '$' + class_name + ret[class_name] = jni_class_path + return ret + + def GetClassPathDefinitions(self): + """Returns the ClassPath constants.""" + ret = [] + template = Template("""\ +const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""") + native_classes = self.GetUniqueClasses(self.natives) + called_by_native_classes = self.GetUniqueClasses(self.called_by_natives) + all_classes = native_classes + all_classes.update(called_by_native_classes) + for clazz in all_classes: + values = { + 'JAVA_CLASS': clazz, + 'JNI_CLASS_PATH': JniParams.RemapClassName(all_classes[clazz]), + } + ret += [template.substitute(values)] + ret += '' + for clazz in called_by_native_classes: + template = Template("""\ +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_${JAVA_CLASS}_clazz = NULL;""") + values = { + 'JAVA_CLASS': clazz, + } + ret += [template.substitute(values)] + return '\n'.join(ret) + + def GetFindClasses(self): + """Returns the imlementation of FindClass for all known classes.""" + template = Template("""\ + g_${JAVA_CLASS}_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, k${JAVA_CLASS}ClassPath).obj()));""") + ret = [] + for clazz in self.GetUniqueClasses(self.called_by_natives): + values = {'JAVA_CLASS': clazz} + ret += [template.substitute(values)] + return '\n'.join(ret) + + def GetMethodIDImpl(self, called_by_native): + """Returns the implementation of GetMethodID.""" + template = Template("""\ + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_${STATIC}>( + env, g_${JAVA_CLASS}_clazz, + "${JNI_NAME}", + ${JNI_SIGNATURE}, + &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); +""") + jni_name = called_by_native.name + jni_return_type = called_by_native.return_type + if called_by_native.is_constructor: + jni_name = '<init>' + jni_return_type = 'void' + values = { + 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, + 'JNI_NAME': jni_name, + 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, + 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE', + 'JNI_SIGNATURE': JniParams.Signature(called_by_native.params, + jni_return_type, + True) + } + return template.substitute(values) + + +def WrapOutput(output): + ret = [] + for line in output.splitlines(): + # Do not wrap lines under 80 characters or preprocessor directives. + if len(line) < 80 or line.lstrip()[:1] == '#': + stripped = line.rstrip() + if len(ret) == 0 or len(ret[-1]) or len(stripped): + ret.append(stripped) + else: + first_line_indent = ' ' * (len(line) - len(line.lstrip())) + subsequent_indent = first_line_indent + ' ' * 4 + if line.startswith('//'): + subsequent_indent = '//' + subsequent_indent + wrapper = textwrap.TextWrapper(width=80, + subsequent_indent=subsequent_indent, + break_long_words=False) + ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)] + ret += [''] + return '\n'.join(ret) + + +def ExtractJarInputFile(jar_file, input_file, out_dir): + """Extracts input file from jar and returns the filename. + + The input file is extracted to the same directory that the generated jni + headers will be placed in. This is passed as an argument to script. + + Args: + jar_file: the jar file containing the input files to extract. + input_files: the list of files to extract from the jar file. + out_dir: the name of the directories to extract to. + + Returns: + the name of extracted input file. + """ + jar_file = zipfile.ZipFile(jar_file) + + out_dir = os.path.join(out_dir, os.path.dirname(input_file)) + try: + os.makedirs(out_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + extracted_file_name = os.path.join(out_dir, os.path.basename(input_file)) + with open(extracted_file_name, 'w') as outfile: + outfile.write(jar_file.read(input_file)) + + return extracted_file_name + + +def GenerateJNIHeader(input_file, output_file, namespace, skip_if_same): + try: + if os.path.splitext(input_file)[1] == '.class': + jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, namespace) + content = jni_from_javap.GetContent() + else: + jni_from_java_source = JNIFromJavaSource.CreateFromFile(input_file) + content = jni_from_java_source.GetContent() + except ParseError, e: + print e + sys.exit(1) + if output_file: + if not os.path.exists(os.path.dirname(os.path.abspath(output_file))): + os.makedirs(os.path.dirname(os.path.abspath(output_file))) + if skip_if_same and os.path.exists(output_file): + with file(output_file, 'r') as f: + existing_content = f.read() + if existing_content == content: + return + with file(output_file, 'w') as f: + f.write(content) + else: + print output + + +def main(argv): + usage = """usage: %prog [OPTIONS] +This script will parse the given java source code extracting the native +declarations and print the header file to stdout (or a file). +See SampleForTests.java for more details. + """ + option_parser = optparse.OptionParser(usage=usage) + option_parser.add_option('-j', dest='jar_file', + help='Extract the list of input files from' + ' a specified jar file.' + ' Uses javap to extract the methods from a' + ' pre-compiled class. --input should point' + ' to pre-compiled Java .class files.') + option_parser.add_option('-n', dest='namespace', + help='Uses as a namespace in the generated header,' + ' instead of the javap class name.') + option_parser.add_option('--input_file', + help='Single input file name. The output file name ' + 'will be derived from it. Must be used with ' + '--output_dir.') + option_parser.add_option('--output_dir', + help='The output directory. Must be used with ' + '--input') + option_parser.add_option('--optimize_generation', type="int", + default=0, help='Whether we should optimize JNI ' + 'generation by not regenerating files if they have ' + 'not changed.') + option_parser.add_option('--jarjar', + help='Path to optional jarjar rules file.') + options, args = option_parser.parse_args(argv) + if options.jar_file: + input_file = ExtractJarInputFile(options.jar_file, options.input_file, + options.output_dir) + else: + input_file = options.input_file + output_file = None + if options.output_dir: + root_name = os.path.splitext(os.path.basename(input_file))[0] + output_file = os.path.join(options.output_dir, root_name) + '_jni.h' + if options.jarjar: + with open(options.jarjar) as f: + JniParams.SetJarJarMappings(f.read()) + GenerateJNIHeader(input_file, output_file, options.namespace, + options.optimize_generation) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/chromium/base/android/jni_generator/jni_generator_tests.py b/chromium/base/android/jni_generator/jni_generator_tests.py new file mode 100755 index 00000000000..f008f394cec --- /dev/null +++ b/chromium/base/android/jni_generator/jni_generator_tests.py @@ -0,0 +1,2084 @@ +#!/usr/bin/env python +# 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. + +"""Tests for jni_generator.py. + +This test suite contains various tests for the JNI generator. +It exercises the low-level parser all the way up to the +code generator and ensures the output matches a golden +file. +""" + +import difflib +import os +import sys +import unittest +import jni_generator +from jni_generator import CalledByNative, JniParams, NativeMethod, Param + + +class TestGenerator(unittest.TestCase): + def assertObjEquals(self, first, second): + dict_first = first.__dict__ + dict_second = second.__dict__ + self.assertEquals(dict_first.keys(), dict_second.keys()) + for key, value in dict_first.iteritems(): + if (type(value) is list and len(value) and + isinstance(type(value[0]), object)): + self.assertListEquals(value, second.__getattribute__(key)) + else: + actual = second.__getattribute__(key) + self.assertEquals(value, actual, + 'Key ' + key + ': ' + str(value) + '!=' + str(actual)) + + def assertListEquals(self, first, second): + self.assertEquals(len(first), len(second)) + for i in xrange(len(first)): + if isinstance(first[i], object): + self.assertObjEquals(first[i], second[i]) + else: + self.assertEquals(first[i], second[i]) + + def assertTextEquals(self, golden_text, generated_text): + stripped_golden = [l.strip() for l in golden_text.split('\n')] + stripped_generated = [l.strip() for l in generated_text.split('\n')] + if stripped_golden != stripped_generated: + print self.id() + for line in difflib.context_diff(stripped_golden, stripped_generated): + print line + print '\n\nGenerated' + print '=' * 80 + print generated_text + print '=' * 80 + self.fail('Golden text mismatch') + + def testNatives(self): + test_data = """" + interface OnFrameAvailableListener {} + private native int nativeInit(); + private native void nativeDestroy(int nativeChromeBrowserProvider); + private native long nativeAddBookmark( + int nativeChromeBrowserProvider, + String url, String title, boolean isFolder, long parentId); + private static native String nativeGetDomainAndRegistry(String url); + private static native void nativeCreateHistoricalTabFromState( + byte[] state, int tab_index); + private native byte[] nativeGetStateAsByteArray(View view); + private static native String[] nativeGetAutofillProfileGUIDs(); + private native void nativeSetRecognitionResults( + int sessionId, String[] results); + private native long nativeAddBookmarkFromAPI( + int nativeChromeBrowserProvider, + String url, Long created, Boolean isBookmark, + Long date, byte[] favicon, String title, Integer visits); + native int nativeFindAll(String find); + private static native OnFrameAvailableListener nativeGetInnerClass(); + private native Bitmap nativeQueryBitmap( + int nativeChromeBrowserProvider, + String[] projection, String selection, + String[] selectionArgs, String sortOrder); + private native void nativeGotOrientation( + int nativeDataFetcherImplAndroid, + double alpha, double beta, double gamma); + """ + jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data) + natives = jni_generator.ExtractNatives(test_data) + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', + params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=False, name='Destroy', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='long', static=False, name='AddBookmark', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String', + name='url'), + Param(datatype='String', + name='title'), + Param(datatype='boolean', + name='isFolder'), + Param(datatype='long', + name='parentId')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='String', static=True, + name='GetDomainAndRegistry', + params=[Param(datatype='String', + name='url')], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=True, + name='CreateHistoricalTabFromState', + params=[Param(datatype='byte[]', + name='state'), + Param(datatype='int', + name='tab_index')], + java_class_name=None, + type='function'), + NativeMethod(return_type='byte[]', static=False, + name='GetStateAsByteArray', + params=[Param(datatype='View', name='view')], + java_class_name=None, + type='function'), + NativeMethod(return_type='String[]', static=True, + name='GetAutofillProfileGUIDs', params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=False, + name='SetRecognitionResults', + params=[Param(datatype='int', name='sessionId'), + Param(datatype='String[]', name='results')], + java_class_name=None, + type='function'), + NativeMethod(return_type='long', static=False, + name='AddBookmarkFromAPI', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String', + name='url'), + Param(datatype='Long', + name='created'), + Param(datatype='Boolean', + name='isBookmark'), + Param(datatype='Long', + name='date'), + Param(datatype='byte[]', + name='favicon'), + Param(datatype='String', + name='title'), + Param(datatype='Integer', + name='visits')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='int', static=False, + name='FindAll', + params=[Param(datatype='String', + name='find')], + java_class_name=None, + type='function'), + NativeMethod(return_type='OnFrameAvailableListener', static=True, + name='GetInnerClass', + params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='Bitmap', + static=False, + name='QueryBitmap', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String[]', + name='projection'), + Param(datatype='String', + name='selection'), + Param(datatype='String[]', + name='selectionArgs'), + Param(datatype='String', + name='sortOrder'), + ], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='void', static=False, + name='GotOrientation', + params=[Param(datatype='int', + name='nativeDataFetcherImplAndroid'), + Param(datatype='double', + name='alpha'), + Param(datatype='double', + name='beta'), + Param(datatype='double', + name='gamma'), + ], + java_class_name=None, + type='method', + p0_type='content::DataFetcherImplAndroid'), + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, []) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_TestJni_clazz = NULL; +} // namespace + +static jint Init(JNIEnv* env, jobject obj); + +static jstring GetDomainAndRegistry(JNIEnv* env, jclass clazz, + jstring url); + +static void CreateHistoricalTabFromState(JNIEnv* env, jclass clazz, + jbyteArray state, + jint tab_index); + +static jbyteArray GetStateAsByteArray(JNIEnv* env, jobject obj, + jobject view); + +static jobjectArray GetAutofillProfileGUIDs(JNIEnv* env, jclass clazz); + +static void SetRecognitionResults(JNIEnv* env, jobject obj, + jint sessionId, + jobjectArray results); + +static jint FindAll(JNIEnv* env, jobject obj, + jstring find); + +static jobject GetInnerClass(JNIEnv* env, jclass clazz); + +// Step 2: method stubs. +static void Destroy(JNIEnv* env, jobject obj, + jint nativeChromeBrowserProvider) { + DCHECK(nativeChromeBrowserProvider) << "Destroy"; + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + return native->Destroy(env, obj); +} + +static jlong AddBookmark(JNIEnv* env, jobject obj, + jint nativeChromeBrowserProvider, + jstring url, + jstring title, + jboolean isFolder, + jlong parentId) { + DCHECK(nativeChromeBrowserProvider) << "AddBookmark"; + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + return native->AddBookmark(env, obj, url, title, isFolder, parentId); +} + +static jlong AddBookmarkFromAPI(JNIEnv* env, jobject obj, + jint nativeChromeBrowserProvider, + jstring url, + jobject created, + jobject isBookmark, + jobject date, + jbyteArray favicon, + jstring title, + jobject visits) { + DCHECK(nativeChromeBrowserProvider) << "AddBookmarkFromAPI"; + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + return native->AddBookmarkFromAPI(env, obj, url, created, isBookmark, date, + favicon, title, visits); +} + +static jobject QueryBitmap(JNIEnv* env, jobject obj, + jint nativeChromeBrowserProvider, + jobjectArray projection, + jstring selection, + jobjectArray selectionArgs, + jstring sortOrder) { + DCHECK(nativeChromeBrowserProvider) << "QueryBitmap"; + ChromeBrowserProvider* native = + reinterpret_cast<ChromeBrowserProvider*>(nativeChromeBrowserProvider); + return native->QueryBitmap(env, obj, projection, selection, selectionArgs, + sortOrder).Release(); +} + +static void GotOrientation(JNIEnv* env, jobject obj, + jint nativeDataFetcherImplAndroid, + jdouble alpha, + jdouble beta, + jdouble gamma) { + DCHECK(nativeDataFetcherImplAndroid) << "GotOrientation"; + DataFetcherImplAndroid* native = + reinterpret_cast<DataFetcherImplAndroid*>(nativeDataFetcherImplAndroid); + return native->GotOrientation(env, obj, alpha, beta, gamma); +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kTestJniClassPath).obj())); + static const JNINativeMethod kMethodsTestJni[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + { "nativeDestroy", +"(" +"I" +")" +"V", reinterpret_cast<void*>(Destroy) }, + { "nativeAddBookmark", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Z" +"J" +")" +"J", reinterpret_cast<void*>(AddBookmark) }, + { "nativeGetDomainAndRegistry", +"(" +"Ljava/lang/String;" +")" +"Ljava/lang/String;", reinterpret_cast<void*>(GetDomainAndRegistry) }, + { "nativeCreateHistoricalTabFromState", +"(" +"[B" +"I" +")" +"V", reinterpret_cast<void*>(CreateHistoricalTabFromState) }, + { "nativeGetStateAsByteArray", +"(" +"Landroid/view/View;" +")" +"[B", reinterpret_cast<void*>(GetStateAsByteArray) }, + { "nativeGetAutofillProfileGUIDs", +"(" +")" +"[Ljava/lang/String;", reinterpret_cast<void*>(GetAutofillProfileGUIDs) }, + { "nativeSetRecognitionResults", +"(" +"I" +"[Ljava/lang/String;" +")" +"V", reinterpret_cast<void*>(SetRecognitionResults) }, + { "nativeAddBookmarkFromAPI", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/Long;" +"Ljava/lang/Boolean;" +"Ljava/lang/Long;" +"[B" +"Ljava/lang/String;" +"Ljava/lang/Integer;" +")" +"J", reinterpret_cast<void*>(AddBookmarkFromAPI) }, + { "nativeFindAll", +"(" +"Ljava/lang/String;" +")" +"I", reinterpret_cast<void*>(FindAll) }, + { "nativeGetInnerClass", +"(" +")" +"Lorg/chromium/example/jni_generator/SampleForTests$OnFrameAvailableListener;", + reinterpret_cast<void*>(GetInnerClass) }, + { "nativeQueryBitmap", +"(" +"I" +"[Ljava/lang/String;" +"Ljava/lang/String;" +"[Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Landroid/graphics/Bitmap;", reinterpret_cast<void*>(QueryBitmap) }, + { "nativeGotOrientation", +"(" +"I" +"D" +"D" +"D" +")" +"V", reinterpret_cast<void*>(GotOrientation) }, + }; + const int kMethodsTestJniSize = arraysize(kMethodsTestJni); + + if (env->RegisterNatives(g_TestJni_clazz, + kMethodsTestJni, + kMethodsTestJniSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI +""" + self.assertTextEquals(golden_content, h.GetContent()) + + def testInnerClassNatives(self): + test_data = """ + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + """ + natives = jni_generator.ExtractNatives(test_data) + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, []) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kMyInnerClassClassPath[] = "org/chromium/TestJni$MyInnerClass"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_TestJni_clazz = NULL; +} // namespace + +static jint Init(JNIEnv* env, jobject obj); + +// Step 2: method stubs. + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kTestJniClassPath).obj())); + static const JNINativeMethod kMethodsMyInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + }; + const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass); + + if (env->RegisterNatives(g_MyInnerClass_clazz, + kMethodsMyInnerClass, + kMethodsMyInnerClassSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI +""" + self.assertTextEquals(golden_content, h.GetContent()) + + def testInnerClassNativesMultiple(self): + test_data = """ + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + """ + natives = jni_generator.ExtractNatives(test_data) + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyInnerClass', + type='function'), + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyOtherInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, []) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kMyOtherInnerClassClassPath[] = + "org/chromium/TestJni$MyOtherInnerClass"; +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kMyInnerClassClassPath[] = "org/chromium/TestJni$MyInnerClass"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_TestJni_clazz = NULL; +} // namespace + +static jint Init(JNIEnv* env, jobject obj); + +static jint Init(JNIEnv* env, jobject obj); + +// Step 2: method stubs. + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kTestJniClassPath).obj())); + static const JNINativeMethod kMethodsMyOtherInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + }; + const int kMethodsMyOtherInnerClassSize = + arraysize(kMethodsMyOtherInnerClass); + + if (env->RegisterNatives(g_MyOtherInnerClass_clazz, + kMethodsMyOtherInnerClass, + kMethodsMyOtherInnerClassSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + static const JNINativeMethod kMethodsMyInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + }; + const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass); + + if (env->RegisterNatives(g_MyInnerClass_clazz, + kMethodsMyInnerClass, + kMethodsMyInnerClassSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI +""" + self.assertTextEquals(golden_content, h.GetContent()) + + def testInnerClassNativesBothInnerAndOuter(self): + test_data = """ + class MyOuterClass { + private native int nativeInit(); + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + } + """ + natives = jni_generator.ExtractNatives(test_data) + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyOtherInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, []) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kMyOtherInnerClassClassPath[] = + "org/chromium/TestJni$MyOtherInnerClass"; +const char kTestJniClassPath[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_TestJni_clazz = NULL; +} // namespace + +static jint Init(JNIEnv* env, jobject obj); + +static jint Init(JNIEnv* env, jobject obj); + +// Step 2: method stubs. + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kTestJniClassPath).obj())); + static const JNINativeMethod kMethodsMyOtherInnerClass[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + }; + const int kMethodsMyOtherInnerClassSize = + arraysize(kMethodsMyOtherInnerClass); + + if (env->RegisterNatives(g_MyOtherInnerClass_clazz, + kMethodsMyOtherInnerClass, + kMethodsMyOtherInnerClassSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + static const JNINativeMethod kMethodsTestJni[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast<void*>(Init) }, + }; + const int kMethodsTestJniSize = arraysize(kMethodsTestJni); + + if (env->RegisterNatives(g_TestJni_clazz, + kMethodsTestJni, + kMethodsTestJniSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI +""" + self.assertTextEquals(golden_content, h.GetContent()) + + def testCalledByNatives(self): + test_data = """" + import android.graphics.Bitmap; + import android.view.View; + import java.io.InputStream; + import java.util.List; + + class InnerClass {} + + @CalledByNative + InnerClass showConfirmInfoBar(int nativeInfoBar, + String buttonOk, String buttonCancel, String title, Bitmap icon) { + InfoBar infobar = new ConfirmInfoBar(nativeInfoBar, mContext, + buttonOk, buttonCancel, + title, icon); + return infobar; + } + @CalledByNative + InnerClass showAutoLoginInfoBar(int nativeInfoBar, + String realm, String account, String args) { + AutoLoginInfoBar infobar = new AutoLoginInfoBar(nativeInfoBar, mContext, + realm, account, args); + if (infobar.displayedAccountCount() == 0) + infobar = null; + return infobar; + } + @CalledByNative("InfoBar") + void dismiss(); + @SuppressWarnings("unused") + @CalledByNative + private static boolean shouldShowAutoLogin(View view, + String realm, String account, String args) { + AccountManagerContainer accountManagerContainer = + new AccountManagerContainer((Activity)contentView.getContext(), + realm, account, args); + String[] logins = accountManagerContainer.getAccountLogins(null); + return logins.length != 0; + } + @CalledByNative + static InputStream openUrl(String url) { + return null; + } + @CalledByNative + private void activateHardwareAcceleration(final boolean activated, + final int iPid, final int iType, + final int iPrimaryID, final int iSecondaryID) { + if (!activated) { + return + } + } + @CalledByNativeUnchecked + private void uncheckedCall(int iParam); + + @CalledByNative + public byte[] returnByteArray(); + + @CalledByNative + public boolean[] returnBooleanArray(); + + @CalledByNative + public char[] returnCharArray(); + + @CalledByNative + public short[] returnShortArray(); + + @CalledByNative + public int[] returnIntArray(); + + @CalledByNative + public long[] returnLongArray(); + + @CalledByNative + public double[] returnDoubleArray(); + + @CalledByNative + public Object[] returnObjectArray(); + + @CalledByNative + public byte[][] returnArrayOfByteArray(); + + @CalledByNative + public Bitmap.CompressFormat getCompressFormat(); + + @CalledByNative + public List<Bitmap.CompressFormat> getCompressFormatList(); + """ + jni_generator.JniParams.SetFullyQualifiedClass('org/chromium/Foo') + jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data) + called_by_natives = jni_generator.ExtractCalledByNatives(test_data) + golden_called_by_natives = [ + CalledByNative( + return_type='InnerClass', + system_class=False, + static=False, + name='showConfirmInfoBar', + method_id_var_name='showConfirmInfoBar', + java_class_name='', + params=[Param(datatype='int', name='nativeInfoBar'), + Param(datatype='String', name='buttonOk'), + Param(datatype='String', name='buttonCancel'), + Param(datatype='String', name='title'), + Param(datatype='Bitmap', name='icon')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='InnerClass', + system_class=False, + static=False, + name='showAutoLoginInfoBar', + method_id_var_name='showAutoLoginInfoBar', + java_class_name='', + params=[Param(datatype='int', name='nativeInfoBar'), + Param(datatype='String', name='realm'), + Param(datatype='String', name='account'), + Param(datatype='String', name='args')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='dismiss', + method_id_var_name='dismiss', + java_class_name='InfoBar', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='boolean', + system_class=False, + static=True, + name='shouldShowAutoLogin', + method_id_var_name='shouldShowAutoLogin', + java_class_name='', + params=[Param(datatype='View', name='view'), + Param(datatype='String', name='realm'), + Param(datatype='String', name='account'), + Param(datatype='String', name='args')], + env_call=('Boolean', ''), + unchecked=False, + ), + CalledByNative( + return_type='InputStream', + system_class=False, + static=True, + name='openUrl', + method_id_var_name='openUrl', + java_class_name='', + params=[Param(datatype='String', name='url')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='activateHardwareAcceleration', + method_id_var_name='activateHardwareAcceleration', + java_class_name='', + params=[Param(datatype='boolean', name='activated'), + Param(datatype='int', name='iPid'), + Param(datatype='int', name='iType'), + Param(datatype='int', name='iPrimaryID'), + Param(datatype='int', name='iSecondaryID'), + ], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='uncheckedCall', + method_id_var_name='uncheckedCall', + java_class_name='', + params=[Param(datatype='int', name='iParam')], + env_call=('Void', ''), + unchecked=True, + ), + CalledByNative( + return_type='byte[]', + system_class=False, + static=False, + name='returnByteArray', + method_id_var_name='returnByteArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='boolean[]', + system_class=False, + static=False, + name='returnBooleanArray', + method_id_var_name='returnBooleanArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='char[]', + system_class=False, + static=False, + name='returnCharArray', + method_id_var_name='returnCharArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='short[]', + system_class=False, + static=False, + name='returnShortArray', + method_id_var_name='returnShortArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='int[]', + system_class=False, + static=False, + name='returnIntArray', + method_id_var_name='returnIntArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='long[]', + system_class=False, + static=False, + name='returnLongArray', + method_id_var_name='returnLongArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='double[]', + system_class=False, + static=False, + name='returnDoubleArray', + method_id_var_name='returnDoubleArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='Object[]', + system_class=False, + static=False, + name='returnObjectArray', + method_id_var_name='returnObjectArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='byte[][]', + system_class=False, + static=False, + name='returnArrayOfByteArray', + method_id_var_name='returnArrayOfByteArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='Bitmap.CompressFormat', + system_class=False, + static=False, + name='getCompressFormat', + method_id_var_name='getCompressFormat', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='List<Bitmap.CompressFormat>', + system_class=False, + static=False, + name='getCompressFormatList', + method_id_var_name='getCompressFormatList', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + ] + self.assertListEquals(golden_called_by_natives, called_by_natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + [], called_by_natives) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kInfoBarClassPath[] = "org/chromium/TestJni$InfoBar"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_TestJni_clazz = NULL; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_InfoBar_clazz = NULL; +} // namespace + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_TestJni_showConfirmInfoBar = 0; +static ScopedJavaLocalRef<jobject> Java_TestJni_showConfirmInfoBar(JNIEnv* env, + jobject obj, jint nativeInfoBar, + jstring buttonOk, + jstring buttonCancel, + jstring title, + jobject icon) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "showConfirmInfoBar", + +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Landroid/graphics/Bitmap;" +")" +"Lorg/chromium/Foo$InnerClass;", + &g_TestJni_showConfirmInfoBar); + + jobject ret = + env->CallObjectMethod(obj, + method_id, nativeInfoBar, buttonOk, buttonCancel, title, icon); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_showAutoLoginInfoBar = 0; +static ScopedJavaLocalRef<jobject> Java_TestJni_showAutoLoginInfoBar(JNIEnv* + env, jobject obj, jint nativeInfoBar, + jstring realm, + jstring account, + jstring args) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "showAutoLoginInfoBar", + +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Lorg/chromium/Foo$InnerClass;", + &g_TestJni_showAutoLoginInfoBar); + + jobject ret = + env->CallObjectMethod(obj, + method_id, nativeInfoBar, realm, account, args); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +static base::subtle::AtomicWord g_InfoBar_dismiss = 0; +static void Java_InfoBar_dismiss(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InfoBar_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InfoBar_clazz, + "dismiss", + +"(" +")" +"V", + &g_InfoBar_dismiss); + + env->CallVoidMethod(obj, + method_id); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_TestJni_shouldShowAutoLogin = 0; +static jboolean Java_TestJni_shouldShowAutoLogin(JNIEnv* env, jobject view, + jstring realm, + jstring account, + jstring args) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, g_TestJni_clazz, + "shouldShowAutoLogin", + +"(" +"Landroid/view/View;" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Z", + &g_TestJni_shouldShowAutoLogin); + + jboolean ret = + env->CallStaticBooleanMethod(g_TestJni_clazz, + method_id, view, realm, account, args); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_TestJni_openUrl = 0; +static ScopedJavaLocalRef<jobject> Java_TestJni_openUrl(JNIEnv* env, jstring + url) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, g_TestJni_clazz, + "openUrl", + +"(" +"Ljava/lang/String;" +")" +"Ljava/io/InputStream;", + &g_TestJni_openUrl); + + jobject ret = + env->CallStaticObjectMethod(g_TestJni_clazz, + method_id, url); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_activateHardwareAcceleration = 0; +static void Java_TestJni_activateHardwareAcceleration(JNIEnv* env, jobject obj, + jboolean activated, + jint iPid, + jint iType, + jint iPrimaryID, + jint iSecondaryID) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "activateHardwareAcceleration", + +"(" +"Z" +"I" +"I" +"I" +"I" +")" +"V", + &g_TestJni_activateHardwareAcceleration); + + env->CallVoidMethod(obj, + method_id, activated, iPid, iType, iPrimaryID, iSecondaryID); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_TestJni_uncheckedCall = 0; +static void Java_TestJni_uncheckedCall(JNIEnv* env, jobject obj, jint iParam) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "uncheckedCall", + +"(" +"I" +")" +"V", + &g_TestJni_uncheckedCall); + + env->CallVoidMethod(obj, + method_id, iParam); + +} + +static base::subtle::AtomicWord g_TestJni_returnByteArray = 0; +static ScopedJavaLocalRef<jbyteArray> Java_TestJni_returnByteArray(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnByteArray", + +"(" +")" +"[B", + &g_TestJni_returnByteArray); + + jbyteArray ret = + static_cast<jbyteArray>(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef<jbyteArray>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnBooleanArray = 0; +static ScopedJavaLocalRef<jbooleanArray> Java_TestJni_returnBooleanArray(JNIEnv* + env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnBooleanArray", + +"(" +")" +"[Z", + &g_TestJni_returnBooleanArray); + + jbooleanArray ret = + static_cast<jbooleanArray>(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef<jbooleanArray>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnCharArray = 0; +static ScopedJavaLocalRef<jcharArray> Java_TestJni_returnCharArray(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnCharArray", + +"(" +")" +"[C", + &g_TestJni_returnCharArray); + + jcharArray ret = + static_cast<jcharArray>(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef<jcharArray>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnShortArray = 0; +static ScopedJavaLocalRef<jshortArray> Java_TestJni_returnShortArray(JNIEnv* + env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnShortArray", + +"(" +")" +"[S", + &g_TestJni_returnShortArray); + + jshortArray ret = + static_cast<jshortArray>(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef<jshortArray>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnIntArray = 0; +static ScopedJavaLocalRef<jintArray> Java_TestJni_returnIntArray(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnIntArray", + +"(" +")" +"[I", + &g_TestJni_returnIntArray); + + jintArray ret = + static_cast<jintArray>(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef<jintArray>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnLongArray = 0; +static ScopedJavaLocalRef<jlongArray> Java_TestJni_returnLongArray(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnLongArray", + +"(" +")" +"[J", + &g_TestJni_returnLongArray); + + jlongArray ret = + static_cast<jlongArray>(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef<jlongArray>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnDoubleArray = 0; +static ScopedJavaLocalRef<jdoubleArray> Java_TestJni_returnDoubleArray(JNIEnv* + env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnDoubleArray", + +"(" +")" +"[D", + &g_TestJni_returnDoubleArray); + + jdoubleArray ret = + static_cast<jdoubleArray>(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef<jdoubleArray>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnObjectArray = 0; +static ScopedJavaLocalRef<jobjectArray> Java_TestJni_returnObjectArray(JNIEnv* + env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnObjectArray", + +"(" +")" +"[Ljava/lang/Object;", + &g_TestJni_returnObjectArray); + + jobjectArray ret = + static_cast<jobjectArray>(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobjectArray>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnArrayOfByteArray = 0; +static ScopedJavaLocalRef<jobjectArray> + Java_TestJni_returnArrayOfByteArray(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "returnArrayOfByteArray", + +"(" +")" +"[[B", + &g_TestJni_returnArrayOfByteArray); + + jobjectArray ret = + static_cast<jobjectArray>(env->CallObjectMethod(obj, + method_id)); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobjectArray>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_getCompressFormat = 0; +static ScopedJavaLocalRef<jobject> Java_TestJni_getCompressFormat(JNIEnv* env, + jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "getCompressFormat", + +"(" +")" +"Landroid/graphics/Bitmap$CompressFormat;", + &g_TestJni_getCompressFormat); + + jobject ret = + env->CallObjectMethod(obj, + method_id); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_getCompressFormatList = 0; +static ScopedJavaLocalRef<jobject> Java_TestJni_getCompressFormatList(JNIEnv* + env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_TestJni_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_TestJni_clazz, + "getCompressFormatList", + +"(" +")" +"Ljava/util/List;", + &g_TestJni_getCompressFormatList); + + jobject ret = + env->CallObjectMethod(obj, + method_id); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_TestJni_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kTestJniClassPath).obj())); + g_InfoBar_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kInfoBarClassPath).obj())); + return true; +} + +#endif // org_chromium_TestJni_JNI +""" + self.assertTextEquals(golden_content, h.GetContent()) + + def testCalledByNativeParseError(self): + try: + jni_generator.ExtractCalledByNatives(""" +@CalledByNative +public static int foo(); // This one is fine + +@CalledByNative +scooby doo +""") + self.fail('Expected a ParseError') + except jni_generator.ParseError, e: + self.assertEquals(('@CalledByNative', 'scooby doo'), e.context_lines) + + def testFullyQualifiedClassName(self): + contents = """ +// Copyright (c) 2010 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.content.browser; + +import org.chromium.base.BuildInfo; +""" + self.assertEquals('org/chromium/content/browser/Foo', + jni_generator.ExtractFullyQualifiedJavaClassName( + 'org/chromium/content/browser/Foo.java', contents)) + self.assertEquals('org/chromium/content/browser/Foo', + jni_generator.ExtractFullyQualifiedJavaClassName( + 'frameworks/Foo.java', contents)) + self.assertRaises(SyntaxError, + jni_generator.ExtractFullyQualifiedJavaClassName, + 'com/foo/Bar', 'no PACKAGE line') + + def testMethodNameMangling(self): + self.assertEquals('closeV', + jni_generator.GetMangledMethodName('close', [], 'void')) + self.assertEquals('readI_AB_I_I', + jni_generator.GetMangledMethodName('read', + [Param(name='p1', + datatype='byte[]'), + Param(name='p2', + datatype='int'), + Param(name='p3', + datatype='int'),], + 'int')) + self.assertEquals('openJIIS_JLS', + jni_generator.GetMangledMethodName('open', + [Param(name='p1', + datatype='java/lang/String'),], + 'java/io/InputStream')) + + def testFromJavaP(self): + contents = """ +public abstract class java.io.InputStream extends java.lang.Object + implements java.io.Closeable{ + public java.io.InputStream(); + public int available() throws java.io.IOException; + public void close() throws java.io.IOException; + public void mark(int); + public boolean markSupported(); + public abstract int read() throws java.io.IOException; + public int read(byte[]) throws java.io.IOException; + public int read(byte[], int, int) throws java.io.IOException; + public synchronized void reset() throws java.io.IOException; + public long skip(long) throws java.io.IOException; +} +""" + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), None) + self.assertEquals(10, len(jni_from_javap.called_by_natives)) + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// java/io/InputStream + +#ifndef java_io_InputStream_JNI +#define java_io_InputStream_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kInputStreamClassPath[] = "java/io/InputStream"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_InputStream_clazz = NULL; +} // namespace + +namespace JNI_InputStream { + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_InputStream_available = 0; +static jint Java_InputStream_available(JNIEnv* env, jobject obj) __attribute__ + ((unused)); +static jint Java_InputStream_available(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "available", + +"(" +")" +"I", + &g_InputStream_available); + + jint ret = + env->CallIntMethod(obj, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_close = 0; +static void Java_InputStream_close(JNIEnv* env, jobject obj) __attribute__ + ((unused)); +static void Java_InputStream_close(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "close", + +"(" +")" +"V", + &g_InputStream_close); + + env->CallVoidMethod(obj, + method_id); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_InputStream_mark = 0; +static void Java_InputStream_mark(JNIEnv* env, jobject obj, jint p0) + __attribute__ ((unused)); +static void Java_InputStream_mark(JNIEnv* env, jobject obj, jint p0) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "mark", + +"(" +"I" +")" +"V", + &g_InputStream_mark); + + env->CallVoidMethod(obj, + method_id, p0); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_InputStream_markSupported = 0; +static jboolean Java_InputStream_markSupported(JNIEnv* env, jobject obj) + __attribute__ ((unused)); +static jboolean Java_InputStream_markSupported(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "markSupported", + +"(" +")" +"Z", + &g_InputStream_markSupported); + + jboolean ret = + env->CallBooleanMethod(obj, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI = 0; +static jint Java_InputStream_readI(JNIEnv* env, jobject obj) __attribute__ + ((unused)); +static jint Java_InputStream_readI(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "read", + +"(" +")" +"I", + &g_InputStream_readI); + + jint ret = + env->CallIntMethod(obj, + method_id); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI_AB = 0; +static jint Java_InputStream_readI_AB(JNIEnv* env, jobject obj, jbyteArray p0) + __attribute__ ((unused)); +static jint Java_InputStream_readI_AB(JNIEnv* env, jobject obj, jbyteArray p0) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "read", + +"(" +"[B" +")" +"I", + &g_InputStream_readI_AB); + + jint ret = + env->CallIntMethod(obj, + method_id, p0); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI_AB_I_I = 0; +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, jobject obj, jbyteArray + p0, + jint p1, + jint p2) __attribute__ ((unused)); +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, jobject obj, jbyteArray + p0, + jint p1, + jint p2) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "read", + +"(" +"[B" +"I" +"I" +")" +"I", + &g_InputStream_readI_AB_I_I); + + jint ret = + env->CallIntMethod(obj, + method_id, p0, p1, p2); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_reset = 0; +static void Java_InputStream_reset(JNIEnv* env, jobject obj) __attribute__ + ((unused)); +static void Java_InputStream_reset(JNIEnv* env, jobject obj) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "reset", + +"(" +")" +"V", + &g_InputStream_reset); + + env->CallVoidMethod(obj, + method_id); + base::android::CheckException(env); + +} + +static base::subtle::AtomicWord g_InputStream_skip = 0; +static jlong Java_InputStream_skip(JNIEnv* env, jobject obj, jlong p0) + __attribute__ ((unused)); +static jlong Java_InputStream_skip(JNIEnv* env, jobject obj, jlong p0) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "skip", + +"(" +"J" +")" +"J", + &g_InputStream_skip); + + jlong ret = + env->CallLongMethod(obj, + method_id, p0); + base::android::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_Constructor = 0; +static ScopedJavaLocalRef<jobject> Java_InputStream_Constructor(JNIEnv* env) + __attribute__ ((unused)); +static ScopedJavaLocalRef<jobject> Java_InputStream_Constructor(JNIEnv* env) { + /* Must call RegisterNativesImpl() */ + DCHECK(g_InputStream_clazz); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, g_InputStream_clazz, + "<init>", + +"(" +")" +"V", + &g_InputStream_Constructor); + + jobject ret = + env->NewObject(g_InputStream_clazz, + method_id); + base::android::CheckException(env); + return ScopedJavaLocalRef<jobject>(env, ret); +} + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_InputStream_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kInputStreamClassPath).obj())); + return true; +} +} // namespace JNI_InputStream + +#endif // java_io_InputStream_JNI +""" + self.assertTextEquals(golden_content, jni_from_javap.GetContent()) + + def testREForNatives(self): + # We should not match "native SyncSetupFlow" inside the comment. + test_data = """ + /** + * Invoked when the setup process is complete so we can disconnect from the + * native-side SyncSetupFlowHandler. + */ + public void destroy() { + Log.v(TAG, "Destroying native SyncSetupFlow"); + if (mNativeSyncSetupFlow != 0) { + nativeSyncSetupEnded(mNativeSyncSetupFlow); + mNativeSyncSetupFlow = 0; + } + } + private native void nativeSyncSetupEnded( + int nativeAndroidSyncSetupFlowHandler); + """ + jni_from_java = jni_generator.JNIFromJavaSource(test_data, 'foo/bar') + + def testRaisesOnNonJNIMethod(self): + test_data = """ + class MyInnerClass { + private int Foo(int p0) { + } + } + """ + self.assertRaises(SyntaxError, + jni_generator.JNIFromJavaSource, + test_data, 'foo/bar') + + def testJniSelfDocumentingExample(self): + script_dir = os.path.dirname(sys.argv[0]) + content = file(os.path.join(script_dir, + 'java/src/org/chromium/example/jni_generator/SampleForTests.java') + ).read() + golden_content = file(os.path.join(script_dir, + 'golden_sample_for_tests_jni.h')).read() + jni_from_java = jni_generator.JNIFromJavaSource( + content, 'org/chromium/example/jni_generator/SampleForTests') + self.assertTextEquals(golden_content, jni_from_java.GetContent()) + + def testNoWrappingPreprocessorLines(self): + test_data = """ + package com.google.lookhowextremelylongiam.snarf.icankeepthisupallday; + + class ReallyLongClassNamesAreAllTheRage { + private static native int nativeTest(); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, ('com/google/lookhowextremelylongiam/snarf/' + 'icankeepthisupallday/ReallyLongClassNamesAreAllTheRage')) + jni_lines = jni_from_java.GetContent().split('\n') + line = filter(lambda line: line.lstrip().startswith('#ifndef'), + jni_lines)[0] + self.assertTrue(len(line) > 80, + ('Expected #ifndef line to be > 80 chars: ', line)) + + def testJarJarRemapping(self): + test_data = """ + package org.chromium.example.jni_generator; + + import org.chromium.example2.Test; + + class Example { + private static native void nativeTest(Test t); + } + """ + jni_generator.JniParams.SetJarJarMappings( + """rule org.chromium.example.** com.test.@1 + rule org.chromium.example2.** org.test2.@0""") + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'org/chromium/example/jni_generator/Example') + jni_generator.JniParams.SetJarJarMappings('') + golden_content = """\ +// 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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator_tests.py +// For +// org/chromium/example/jni_generator/Example + +#ifndef org_chromium_example_jni_generator_Example_JNI +#define org_chromium_example_jni_generator_Example_JNI + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/logging.h" + +using base::android::ScopedJavaLocalRef; + +// Step 1: forward declarations. +namespace { +const char kExampleClassPath[] = "com/test/jni_generator/Example"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +jclass g_Example_clazz = NULL; +} // namespace + +static void Test(JNIEnv* env, jclass clazz, + jobject t); + +// Step 2: method stubs. + +// Step 3: RegisterNatives. + +static bool RegisterNativesImpl(JNIEnv* env) { + + g_Example_clazz = reinterpret_cast<jclass>(env->NewGlobalRef( + base::android::GetClass(env, kExampleClassPath).obj())); + static const JNINativeMethod kMethodsExample[] = { + { "nativeTest", +"(" +"Lorg/test2/org/chromium/example2/Test;" +")" +"V", reinterpret_cast<void*>(Test) }, + }; + const int kMethodsExampleSize = arraysize(kMethodsExample); + + if (env->RegisterNatives(g_Example_clazz, + kMethodsExample, + kMethodsExampleSize) < 0) { + LOG(ERROR) << "RegisterNatives failed in " << __FILE__; + return false; + } + + return true; +} + +#endif // org_chromium_example_jni_generator_Example_JNI +""" + self.assertTextEquals(golden_content, jni_from_java.GetContent()) + + def testImports(self): + import_header = """ +// 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 org.chromium.content.app; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.SurfaceTexture; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; +import android.view.Surface; + +import java.util.ArrayList; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.content.app.ContentMain; +import org.chromium.content.browser.SandboxedProcessConnection; +import org.chromium.content.common.ISandboxedProcessCallback; +import org.chromium.content.common.ISandboxedProcessService; +import org.chromium.content.common.WillNotRaise.AnException; +import org.chromium.content.common.WillRaise.AnException; + +import static org.chromium.Bar.Zoo; + +class Foo { + public static class BookmarkNode implements Parcelable { + } + public interface PasswordListObserver { + } +} + """ + jni_generator.JniParams.SetFullyQualifiedClass( + 'org/chromium/content/app/Foo') + jni_generator.JniParams.ExtractImportsAndInnerClasses(import_header) + self.assertTrue('Lorg/chromium/content/common/ISandboxedProcessService' in + jni_generator.JniParams._imports) + self.assertTrue('Lorg/chromium/Bar/Zoo' in + jni_generator.JniParams._imports) + self.assertTrue('Lorg/chromium/content/app/Foo$BookmarkNode' in + jni_generator.JniParams._inner_classes) + self.assertTrue('Lorg/chromium/content/app/Foo$PasswordListObserver' in + jni_generator.JniParams._inner_classes) + self.assertEquals('Lorg/chromium/content/app/ContentMain$Inner;', + jni_generator.JniParams.JavaToJni('ContentMain.Inner')) + self.assertRaises(SyntaxError, + jni_generator.JniParams.JavaToJni, + 'AnException') + + def testJniParamsJavaToJni(self): + self.assertTextEquals('I', JniParams.JavaToJni('int')) + self.assertTextEquals('[B', JniParams.JavaToJni('byte[]')) + self.assertTextEquals( + '[Ljava/nio/ByteBuffer;', JniParams.JavaToJni('java/nio/ByteBuffer[]')) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/base/android/jni_generator/sample_for_tests.cc b/chromium/base/android/jni_generator/sample_for_tests.cc new file mode 100644 index 00000000000..30b99406350 --- /dev/null +++ b/chromium/base/android/jni_generator/sample_for_tests.cc @@ -0,0 +1,106 @@ +// 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. + +#include "base/android/jni_generator/sample_for_tests.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "jni/SampleForTests_jni.h" + + +using base::android::AttachCurrentThread; +using base::android::ScopedJavaLocalRef; + +namespace base { +namespace android { + +jdouble CPPClass::InnerClass::MethodOtherP0(JNIEnv* env, jobject obj) { + return 0.0; +} + +CPPClass::CPPClass() { +} + +CPPClass::~CPPClass() { +} + +void CPPClass::Destroy(JNIEnv* env, jobject obj) { + delete this; +} + +jint CPPClass::Method(JNIEnv* env, jobject obj) { + return 0; +} + +void CPPClass::AddStructB(JNIEnv* env, jobject obj, jobject structb) { + long key = Java_InnerStructB_getKey(env, structb); + std::string value = ConvertJavaStringToUTF8( + env, Java_InnerStructB_getValue(env, structb).obj()); + map_[key] = value; +} + +void CPPClass::IterateAndDoSomethingWithStructB(JNIEnv* env, jobject obj) { + // Iterate over the elements and do something with them. + for (std::map<long, std::string>::const_iterator it = map_.begin(); + it != map_.end(); ++it) { + long key = it->first; + std::string value = it->second; + } + map_.clear(); +} + +// Static free functions declared and called directly from java. +static jint Init(JNIEnv* env, jobject obj, jstring param) { + return 0; +} + +static jdouble GetDoubleFunction(JNIEnv*, jobject) { + return 0; +} + +static jfloat GetFloatFunction(JNIEnv*, jclass) { + return 0; +} + +static void SetNonPODDatatype(JNIEnv*, jobject, jobject) { +} + +static jobject GetNonPODDatatype(JNIEnv*, jobject) { + return NULL; +} + +static jint InnerFunction(JNIEnv*, jclass) { + return 0; +} + +} // namespace android +} // namespace base + +int main() { + // On a regular application, you'd call AttachCurrentThread(). This sample is + // not yet linking with all the libraries. + JNIEnv* env = /* AttachCurrentThread() */ NULL; + + // This is how you call a java static method from C++. + bool foo = base::android::Java_SampleForTests_staticJavaMethod(env); + + // This is how you call a java method from C++. Note that you must have + // obtained the jobject somehow. + jobject my_java_object = NULL; + int bar = base::android::Java_SampleForTests_javaMethod( + env, my_java_object, 1, 2); + + for (int i = 0; i < 10; ++i) { + // Creates a "struct" that will then be used by the java side. + ScopedJavaLocalRef<jobject> struct_a = + base::android::Java_InnerStructA_create( + env, 0, 1, + base::android::ConvertUTF8ToJavaString(env, "test").obj()); + base::android::Java_SampleForTests_addStructA( + env, my_java_object, struct_a.obj()); + } + base::android::Java_SampleForTests_iterateAndDoSomething(env, my_java_object); + return 0; +} diff --git a/chromium/base/android/jni_generator/sample_for_tests.h b/chromium/base/android/jni_generator/sample_for_tests.h new file mode 100644 index 00000000000..278008b2df7 --- /dev/null +++ b/chromium/base/android/jni_generator/sample_for_tests.h @@ -0,0 +1,46 @@ +// Copyright (c) 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 <jni.h> +#include <map> +#include <string> + +#include "base/basictypes.h" + +namespace base { +namespace android { + +// This file is used to: +// - document the best practices and guidelines on JNI usage. +// - ensure sample_for_tests_jni.h compiles and the functions declared in it +// as expected. +// +// All methods are called directly from Java. See more documentation in +// SampleForTests.java. +class CPPClass { + public: + CPPClass(); + ~CPPClass(); + + class InnerClass { + public: + jdouble MethodOtherP0(JNIEnv* env, jobject obj); + }; + + void Destroy(JNIEnv* env, jobject obj); + + jint Method(JNIEnv* env, jobject obj); + + void AddStructB(JNIEnv* env, jobject obj, jobject structb); + + void IterateAndDoSomethingWithStructB(JNIEnv* env, jobject obj); + + private: + std::map<long, std::string> map_; + + DISALLOW_COPY_AND_ASSIGN(CPPClass); +}; + +} // namespace android +} // namespace base diff --git a/chromium/base/android/jni_helper.cc b/chromium/base/android/jni_helper.cc new file mode 100644 index 00000000000..fcb610e1884 --- /dev/null +++ b/chromium/base/android/jni_helper.cc @@ -0,0 +1,67 @@ +// 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. + +#include "base/android/jni_helper.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" + +using base::android::AttachCurrentThread; + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef() + : obj_(NULL) { +} + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef( + const JavaObjectWeakGlobalRef& orig) + : obj_(NULL) { + Assign(orig); +} + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj) + : obj_(env->NewWeakGlobalRef(obj)) { + DCHECK(obj_); +} + +JavaObjectWeakGlobalRef::~JavaObjectWeakGlobalRef() { + reset(); +} + +void JavaObjectWeakGlobalRef::operator=(const JavaObjectWeakGlobalRef& rhs) { + Assign(rhs); +} + +void JavaObjectWeakGlobalRef::reset() { + if (obj_) { + AttachCurrentThread()->DeleteWeakGlobalRef(obj_); + obj_ = NULL; + } +} + +base::android::ScopedJavaLocalRef<jobject> + JavaObjectWeakGlobalRef::get(JNIEnv* env) const { + return GetRealObject(env, obj_); +} + +base::android::ScopedJavaLocalRef<jobject> GetRealObject( + JNIEnv* env, jweak obj) { + jobject real = NULL; + if (obj) { + real = env->NewLocalRef(obj); + if (!real) + DLOG(ERROR) << "The real object has been deleted!"; + } + return base::android::ScopedJavaLocalRef<jobject>(env, real); +} + +void JavaObjectWeakGlobalRef::Assign(const JavaObjectWeakGlobalRef& other) { + if (&other == this) + return; + + JNIEnv* env = AttachCurrentThread(); + if (obj_) + env->DeleteWeakGlobalRef(obj_); + + obj_ = other.obj_ ? env->NewWeakGlobalRef(other.obj_) : NULL; +} diff --git a/chromium/base/android/jni_helper.h b/chromium/base/android/jni_helper.h new file mode 100644 index 00000000000..895bf95a9d1 --- /dev/null +++ b/chromium/base/android/jni_helper.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef BASE_ANDROID_JNI_HELPER_H_ +#define BASE_ANDROID_JNI_HELPER_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "base/android/scoped_java_ref.h" + +// Manages WeakGlobalRef lifecycle. +// This class is not thread-safe w.r.t. get() and reset(). Multiple threads may +// safely use get() concurrently, but if the user calls reset() (or of course, +// calls the destructor) they'll need to provide their own synchronization. +class BASE_EXPORT JavaObjectWeakGlobalRef { + public: + JavaObjectWeakGlobalRef(); + JavaObjectWeakGlobalRef(const JavaObjectWeakGlobalRef& orig); + JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj); + virtual ~JavaObjectWeakGlobalRef(); + + void operator=(const JavaObjectWeakGlobalRef& rhs); + + base::android::ScopedJavaLocalRef<jobject> get(JNIEnv* env) const; + + void reset(); + + private: + void Assign(const JavaObjectWeakGlobalRef& rhs); + + jweak obj_; +}; + +// Get the real object stored in the weak reference returned as a +// ScopedJavaLocalRef. +BASE_EXPORT base::android::ScopedJavaLocalRef<jobject> GetRealObject( + JNIEnv* env, jweak obj); + +#endif // BASE_ANDROID_JNI_HELPER_H_ diff --git a/chromium/base/android/jni_registrar.cc b/chromium/base/android/jni_registrar.cc new file mode 100644 index 00000000000..0f32089d18c --- /dev/null +++ b/chromium/base/android/jni_registrar.cc @@ -0,0 +1,30 @@ +// 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. + +#include "base/android/jni_registrar.h" + +#include "base/debug/trace_event.h" +#include "base/logging.h" +#include "base/android/jni_android.h" + +namespace base { +namespace android { + +bool RegisterNativeMethods(JNIEnv* env, + const RegistrationMethod* method, + size_t count) { + TRACE_EVENT0("startup", "base_android::RegisterNativeMethods") + const RegistrationMethod* end = method + count; + while (method != end) { + if (!method->func(env)) { + DLOG(ERROR) << method->name << " failed registration!"; + return false; + } + method++; + } + return true; +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/jni_registrar.h b/chromium/base/android/jni_registrar.h new file mode 100644 index 00000000000..849d07f9392 --- /dev/null +++ b/chromium/base/android/jni_registrar.h @@ -0,0 +1,27 @@ +// 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. + +#ifndef BASE_ANDROID_JNI_REGISTRAR_H_ +#define BASE_ANDROID_JNI_REGISTRAR_H_ + +#include <jni.h> +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace android { + +struct RegistrationMethod; + +// Registers the JNI bindings for the specified |method| definition containing +// |count| elements. Returns whether the registration of the given methods +// succeeded. +BASE_EXPORT bool RegisterNativeMethods(JNIEnv* env, + const RegistrationMethod* method, + size_t count); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_REGISTRAR_H_ diff --git a/chromium/base/android/jni_string.cc b/chromium/base/android/jni_string.cc new file mode 100644 index 00000000000..d25fed822bc --- /dev/null +++ b/chromium/base/android/jni_string.cc @@ -0,0 +1,99 @@ +// 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. + +#include "base/android/jni_string.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace { + +// Internal version that does not use a scoped local pointer. +jstring ConvertUTF16ToJavaStringImpl(JNIEnv* env, + const base::StringPiece16& str) { + jstring result = env->NewString(str.data(), str.length()); + base::android::CheckException(env); + return result; +} + +} + +namespace base { +namespace android { + +void ConvertJavaStringToUTF8(JNIEnv* env, jstring str, std::string* result) { + if (!str) { + LOG(WARNING) << "ConvertJavaStringToUTF8 called with null string."; + result->clear(); + return; + } + // JNI's GetStringUTFChars() returns strings in Java "modified" UTF8, so + // instead get the String in UTF16 and convert using chromium's conversion + // function that yields plain (non Java-modified) UTF8. + const jchar* chars = env->GetStringChars(str, NULL); + DCHECK(chars); + UTF16ToUTF8(chars, env->GetStringLength(str), result); + env->ReleaseStringChars(str, chars); + CheckException(env); +} + +std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str) { + std::string result; + ConvertJavaStringToUTF8(env, str, &result); + return result; +} + +std::string ConvertJavaStringToUTF8(const JavaRef<jstring>& str) { + return ConvertJavaStringToUTF8(AttachCurrentThread(), str.obj()); +} + +ScopedJavaLocalRef<jstring> ConvertUTF8ToJavaString( + JNIEnv* env, + const base::StringPiece& str) { + // JNI's NewStringUTF expects "modified" UTF8 so instead create the string + // via our own UTF16 conversion utility. + // Further, Dalvik requires the string passed into NewStringUTF() to come from + // a trusted source. We can't guarantee that all UTF8 will be sanitized before + // it gets here, so constructing via UTF16 side-steps this issue. + // (Dalvik stores strings internally as UTF16 anyway, so there shouldn't be + // a significant performance hit by doing it this way). + return ScopedJavaLocalRef<jstring>(env, ConvertUTF16ToJavaStringImpl( + env, UTF8ToUTF16(str))); +} + +void ConvertJavaStringToUTF16(JNIEnv* env, jstring str, string16* result) { + if (!str) { + LOG(WARNING) << "ConvertJavaStringToUTF16 called with null string."; + result->clear(); + return; + } + const jchar* chars = env->GetStringChars(str, NULL); + DCHECK(chars); + // GetStringChars isn't required to NULL-terminate the strings + // it returns, so the length must be explicitly checked. + result->assign(chars, env->GetStringLength(str)); + env->ReleaseStringChars(str, chars); + CheckException(env); +} + +string16 ConvertJavaStringToUTF16(JNIEnv* env, jstring str) { + string16 result; + ConvertJavaStringToUTF16(env, str, &result); + return result; +} + +string16 ConvertJavaStringToUTF16(const JavaRef<jstring>& str) { + return ConvertJavaStringToUTF16(AttachCurrentThread(), str.obj()); +} + +ScopedJavaLocalRef<jstring> ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str) { + return ScopedJavaLocalRef<jstring>(env, + ConvertUTF16ToJavaStringImpl(env, str)); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/jni_string.h b/chromium/base/android/jni_string.h new file mode 100644 index 00000000000..89af5b0a897 --- /dev/null +++ b/chromium/base/android/jni_string.h @@ -0,0 +1,45 @@ +// 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. + +#ifndef BASE_ANDROID_JNI_STRING_H_ +#define BASE_ANDROID_JNI_STRING_H_ + +#include <jni.h> +#include <string> + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" +#include "base/strings/string_piece.h" + +namespace base { +namespace android { + +// Convert a Java string to UTF8. Returns a std string. +BASE_EXPORT void ConvertJavaStringToUTF8(JNIEnv* env, + jstring str, + std::string* result); +BASE_EXPORT std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str); +BASE_EXPORT std::string ConvertJavaStringToUTF8(const JavaRef<jstring>& str); + +// Convert a std string to Java string. +BASE_EXPORT ScopedJavaLocalRef<jstring> ConvertUTF8ToJavaString( + JNIEnv* env, + const base::StringPiece& str); + +// Convert a Java string to UTF16. Returns a string16. +BASE_EXPORT void ConvertJavaStringToUTF16(JNIEnv* env, + jstring str, + string16* result); +BASE_EXPORT string16 ConvertJavaStringToUTF16(JNIEnv* env, jstring str); +BASE_EXPORT string16 ConvertJavaStringToUTF16(const JavaRef<jstring>& str); + +// Convert a string16 to a Java string. +BASE_EXPORT ScopedJavaLocalRef<jstring> ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_STRING_H_ diff --git a/chromium/base/android/jni_string_unittest.cc b/chromium/base/android/jni_string_unittest.cc new file mode 100644 index 00000000000..abd0683170e --- /dev/null +++ b/chromium/base/android/jni_string_unittest.cc @@ -0,0 +1,32 @@ +// 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. + +#include "base/android/jni_string.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(JniString, BasicConversionsUTF8) { + const std::string kSimpleString = "SimpleTest8"; + JNIEnv* env = AttachCurrentThread(); + std::string result = + ConvertJavaStringToUTF8(ConvertUTF8ToJavaString(env, kSimpleString)); + EXPECT_EQ(kSimpleString, result); +} + +TEST(JniString, BasicConversionsUTF16) { + const string16 kSimpleString = UTF8ToUTF16("SimpleTest16"); + JNIEnv* env = AttachCurrentThread(); + string16 result = + ConvertJavaStringToUTF16(ConvertUTF16ToJavaString(env, kSimpleString)); + EXPECT_EQ(kSimpleString, result); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/memory_pressure_listener_android.cc b/chromium/base/android/memory_pressure_listener_android.cc new file mode 100644 index 00000000000..80c07bcf9c1 --- /dev/null +++ b/chromium/base/android/memory_pressure_listener_android.cc @@ -0,0 +1,31 @@ +// 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/memory_pressure_listener_android.h" + +#include "base/memory/memory_pressure_listener.h" +#include "jni/MemoryPressureListener_jni.h" + +// Defined and called by JNI. +static void OnMemoryPressure( + JNIEnv* env, jclass clazz, jint memory_pressure_level) { + base::MemoryPressureListener::NotifyMemoryPressure( + static_cast<base::MemoryPressureListener::MemoryPressureLevel>( + memory_pressure_level)); +} + +namespace base { +namespace android { + +bool MemoryPressureListenerAndroid::Register(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +void MemoryPressureListenerAndroid::RegisterSystemCallback(JNIEnv* env) { + Java_MemoryPressureListener_registerSystemCallback( + env, GetApplicationContext()); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/memory_pressure_listener_android.h b/chromium/base/android/memory_pressure_listener_android.h new file mode 100644 index 00000000000..eed8dbb5756 --- /dev/null +++ b/chromium/base/android/memory_pressure_listener_android.h @@ -0,0 +1,30 @@ +// 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. + +#ifndef BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_ +#define BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_ + +#include "base/android/jni_android.h" + +namespace base { +namespace android { + +// Implements the C++ counter part of MemoryPressureListener.java +class BASE_EXPORT MemoryPressureListenerAndroid { + public: + static bool Register(JNIEnv* env); + + static void RegisterSystemCallback(JNIEnv* env); + + // Called by JNI. + static void OnMemoryPressure(int memory_pressure_type); + + private: + DISALLOW_COPY_AND_ASSIGN(MemoryPressureListenerAndroid); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_MEMORY_PRESSURE_LISTENER_ANDROID_H_ diff --git a/chromium/base/android/path_service_android.cc b/chromium/base/android/path_service_android.cc new file mode 100644 index 00000000000..18ca70c8abd --- /dev/null +++ b/chromium/base/android/path_service_android.cc @@ -0,0 +1,26 @@ +// 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. + +#include "base/android/path_service_android.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "jni/PathService_jni.h" + +namespace base { +namespace android { + +void Override(JNIEnv* env, jclass clazz, jint what, jstring path) { + FilePath file_path(ConvertJavaStringToUTF8(env, path)); + PathService::Override(what, file_path); +} + +bool RegisterPathService(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/path_service_android.h b/chromium/base/android/path_service_android.h new file mode 100644 index 00000000000..26040f97beb --- /dev/null +++ b/chromium/base/android/path_service_android.h @@ -0,0 +1,18 @@ +// 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. + +#ifndef BASE_ANDROID_PATH_SERVICE_ANDROID_H_ +#define BASE_ANDROID_PATH_SERVICE_ANDROID_H_ + +#include <jni.h> + +namespace base { +namespace android { + +bool RegisterPathService(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_PATH_SERVICE_ANDROID_H_ diff --git a/chromium/base/android/path_utils.cc b/chromium/base/android/path_utils.cc new file mode 100644 index 00000000000..5737227f38f --- /dev/null +++ b/chromium/base/android/path_utils.cc @@ -0,0 +1,67 @@ +// 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. + +#include "base/android/path_utils.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/files/file_path.h" + +#include "jni/PathUtils_jni.h" + +namespace base { +namespace android { + +bool GetDataDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getDataDirectory(env, GetApplicationContext()); + FilePath data_path(ConvertJavaStringToUTF8(path)); + *result = data_path; + return true; +} + +bool GetCacheDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getCacheDirectory(env, GetApplicationContext()); + FilePath cache_path(ConvertJavaStringToUTF8(path)); + *result = cache_path; + return true; +} + +bool GetDownloadsDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getDownloadsDirectory(env, GetApplicationContext()); + FilePath downloads_path(ConvertJavaStringToUTF8(path)); + *result = downloads_path; + return true; +} + +bool GetNativeLibraryDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getNativeLibraryDirectory(env, GetApplicationContext()); + FilePath library_path(ConvertJavaStringToUTF8(path)); + *result = library_path; + return true; +} + +bool GetExternalStorageDirectory(FilePath* result) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> path = + Java_PathUtils_getExternalStorageDirectory(env); + FilePath storage_path(ConvertJavaStringToUTF8(path)); + *result = storage_path; + return true; +} + +bool RegisterPathUtils(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/path_utils.h b/chromium/base/android/path_utils.h new file mode 100644 index 00000000000..60f8a79b1e2 --- /dev/null +++ b/chromium/base/android/path_utils.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef BASE_ANDROID_PATH_UTILS_H_ +#define BASE_ANDROID_PATH_UTILS_H_ + +#include <jni.h> + +#include "base/base_export.h" + +namespace base { + +class FilePath; + +namespace android { + +// Retrieves the absolute path to the data directory of the current +// application. The result is placed in the FilePath pointed to by 'result'. +// This method is dedicated for base_paths_android.c, Using +// PathService::Get(base::DIR_ANDROID_APP_DATA, ...) gets the data dir. +BASE_EXPORT bool GetDataDirectory(FilePath* result); + +// Retrieves the absolute path to the cache directory. The result is placed in +// the FilePath pointed to by 'result'. This method is dedicated for +// base_paths_android.c, Using PathService::Get(base::DIR_CACHE, ...) gets the +// cache dir. +BASE_EXPORT bool GetCacheDirectory(FilePath* result); + +// Retrieves the path to the public downloads directory. The result is placed +// in the FilePath pointed to by 'result'. +BASE_EXPORT bool GetDownloadsDirectory(FilePath* result); + +// Retrieves the path to the native JNI libraries via +// ApplicationInfo.nativeLibraryDir on the Java side. The result is placed in +// the FilePath pointed to by 'result'. +BASE_EXPORT bool GetNativeLibraryDirectory(FilePath* result); + +// Retrieves the absolute path to the external storage directory. The result +// is placed in the FilePath pointed to by 'result'. +BASE_EXPORT bool GetExternalStorageDirectory(FilePath* result); + +bool RegisterPathUtils(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_PATH_UTILS_H_ diff --git a/chromium/base/android/path_utils_unittest.cc b/chromium/base/android/path_utils_unittest.cc new file mode 100644 index 00000000000..c4c12fea133 --- /dev/null +++ b/chromium/base/android/path_utils_unittest.cc @@ -0,0 +1,46 @@ +// 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. + +#include "base/android/path_utils.h" +#include "base/file_util.h" +#include "base/files/file_path.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +typedef testing::Test PathUtilsTest; + +TEST_F(PathUtilsTest, TestGetDataDirectory) { + // The string comes from the Java side and depends on the APK + // we are running in. Assumes that we are packaged in + // org.chromium.native_test + FilePath path; + GetDataDirectory(&path); + EXPECT_STREQ("/data/data/org.chromium.native_test/app_chrome", + path.value().c_str()); +} + +TEST_F(PathUtilsTest, TestGetCacheDirectory) { + // The string comes from the Java side and depends on the APK + // we are running in. Assumes that we are packaged in + // org.chromium.native_test + FilePath path; + GetCacheDirectory(&path); + EXPECT_STREQ("/data/data/org.chromium.native_test/cache", + path.value().c_str()); +} + +TEST_F(PathUtilsTest, TestGetNativeLibraryDirectory) { + // The string comes from the Java side and depends on the APK + // we are running in. Assumes that the directory contains + // the base tests shared object. + FilePath path; + GetNativeLibraryDirectory(&path); + EXPECT_TRUE(base::PathExists(path.Append(("libbase_unittests.so")))); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/scoped_java_ref.cc b/chromium/base/android/scoped_java_ref.cc new file mode 100644 index 00000000000..21b466e9584 --- /dev/null +++ b/chromium/base/android/scoped_java_ref.cc @@ -0,0 +1,73 @@ +// 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. + +#include "base/android/scoped_java_ref.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" + +namespace base { +namespace android { + +JavaRef<jobject>::JavaRef() : obj_(NULL) {} + +JavaRef<jobject>::JavaRef(JNIEnv* env, jobject obj) : obj_(obj) { + if (obj) { + DCHECK(env && env->GetObjectRefType(obj) == JNILocalRefType); + } +} + +JavaRef<jobject>::~JavaRef() { +} + +JNIEnv* JavaRef<jobject>::SetNewLocalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewLocalRef(obj); + if (obj_) + env->DeleteLocalRef(obj_); + obj_ = obj; + return env; +} + +void JavaRef<jobject>::SetNewGlobalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewGlobalRef(obj); + if (obj_) + env->DeleteGlobalRef(obj_); + obj_ = obj; +} + +void JavaRef<jobject>::ResetLocalRef(JNIEnv* env) { + if (obj_) { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + env->DeleteLocalRef(obj_); + obj_ = NULL; + } +} + +void JavaRef<jobject>::ResetGlobalRef() { + if (obj_) { + AttachCurrentThread()->DeleteGlobalRef(obj_); + obj_ = NULL; + } +} + +jobject JavaRef<jobject>::ReleaseInternal() { + jobject obj = obj_; + obj_ = NULL; + return obj; +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/scoped_java_ref.h b/chromium/base/android/scoped_java_ref.h new file mode 100644 index 00000000000..a5d71e2d23b --- /dev/null +++ b/chromium/base/android/scoped_java_ref.h @@ -0,0 +1,198 @@ +// 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. + +#ifndef BASE_ANDROID_SCOPED_JAVA_REF_H_ +#define BASE_ANDROID_SCOPED_JAVA_REF_H_ + +#include <jni.h> +#include <stddef.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { +namespace android { + +// Forward declare the generic java reference template class. +template<typename T> class JavaRef; + +// Template specialization of JavaRef, which acts as the base class for all +// other JavaRef<> template types. This allows you to e.g. pass +// ScopedJavaLocalRef<jstring> into a function taking const JavaRef<jobject>& +template<> +class BASE_EXPORT JavaRef<jobject> { + public: + jobject obj() const { return obj_; } + + bool is_null() const { return obj_ == NULL; } + + protected: + // Initializes a NULL reference. + JavaRef(); + + // Takes ownership of the |obj| reference passed; requires it to be a local + // reference type. + JavaRef(JNIEnv* env, jobject obj); + + ~JavaRef(); + + // The following are implementation detail convenience methods, for + // use by the sub-classes. + JNIEnv* SetNewLocalRef(JNIEnv* env, jobject obj); + void SetNewGlobalRef(JNIEnv* env, jobject obj); + void ResetLocalRef(JNIEnv* env); + void ResetGlobalRef(); + jobject ReleaseInternal(); + + private: + jobject obj_; + + DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Generic base class for ScopedJavaLocalRef and ScopedJavaGlobalRef. Useful +// for allowing functions to accept a reference without having to mandate +// whether it is a local or global type. +template<typename T> +class JavaRef : public JavaRef<jobject> { + public: + T obj() const { return static_cast<T>(JavaRef<jobject>::obj()); } + + protected: + JavaRef() {} + ~JavaRef() {} + + JavaRef(JNIEnv* env, T obj) : JavaRef<jobject>(env, obj) {} + + private: + DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Holds a local reference to a Java object. The local reference is scoped +// to the lifetime of this object. +// Instances of this class may hold onto any JNIEnv passed into it until +// destroyed. Therefore, since a JNIEnv is only suitable for use on a single +// thread, objects of this class must be created, used, and destroyed, on a +// single thread. +// Therefore, this class should only be used as a stack-based object and from a +// single thread. If you wish to have the reference outlive the current +// callstack (e.g. as a class member) or you wish to pass it across threads, +// use a ScopedJavaGlobalRef instead. +template<typename T> +class ScopedJavaLocalRef : public JavaRef<T> { + public: + ScopedJavaLocalRef() : env_(NULL) {} + + // Non-explicit copy constructor, to allow ScopedJavaLocalRef to be returned + // by value as this is the normal usage pattern. + ScopedJavaLocalRef(const ScopedJavaLocalRef<T>& other) + : env_(other.env_) { + this->SetNewLocalRef(env_, other.obj()); + } + + template<typename U> + explicit ScopedJavaLocalRef(const U& other) + : env_(NULL) { + this->Reset(other); + } + + // Assumes that |obj| is a local reference to a Java object and takes + // ownership of this local reference. + ScopedJavaLocalRef(JNIEnv* env, T obj) : JavaRef<T>(env, obj), env_(env) {} + + ~ScopedJavaLocalRef() { + this->Reset(); + } + + // Overloaded assignment operator defined for consistency with the implicit + // copy constructor. + void operator=(const ScopedJavaLocalRef<T>& other) { + this->Reset(other); + } + + void Reset() { + this->ResetLocalRef(env_); + } + + template<typename U> + void Reset(const ScopedJavaLocalRef<U>& other) { + // We can copy over env_ here as |other| instance must be from the same + // thread as |this| local ref. (See class comment for multi-threading + // limitations, and alternatives). + this->Reset(other.env_, other.obj()); + } + + template<typename U> + void Reset(const U& other) { + // If |env_| was not yet set (is still NULL) it will be attached to the + // current thread in SetNewLocalRef(). + this->Reset(env_, other.obj()); + } + + template<typename U> + void Reset(JNIEnv* env, U obj) { + implicit_cast<T>(obj); // Ensure U is assignable to T + env_ = this->SetNewLocalRef(env, obj); + } + + // Releases the local reference to the caller. The caller *must* delete the + // local reference when it is done with it. + T Release() { + return static_cast<T>(this->ReleaseInternal()); + } + + private: + // This class is only good for use on the thread it was created on so + // it's safe to cache the non-threadsafe JNIEnv* inside this object. + JNIEnv* env_; +}; + +// Holds a global reference to a Java object. The global reference is scoped +// to the lifetime of this object. This class does not hold onto any JNIEnv* +// passed to it, hence it is safe to use across threads (within the constraints +// imposed by the underlying Java object that it references). +template<typename T> +class ScopedJavaGlobalRef : public JavaRef<T> { + public: + ScopedJavaGlobalRef() {} + + explicit ScopedJavaGlobalRef(const ScopedJavaGlobalRef<T>& other) { + this->Reset(other); + } + + template<typename U> + explicit ScopedJavaGlobalRef(const U& other) { + this->Reset(other); + } + + ~ScopedJavaGlobalRef() { + this->Reset(); + } + + void Reset() { + this->ResetGlobalRef(); + } + + template<typename U> + void Reset(const U& other) { + this->Reset(NULL, other.obj()); + } + + template<typename U> + void Reset(JNIEnv* env, U obj) { + implicit_cast<T>(obj); // Ensure U is assignable to T + this->SetNewGlobalRef(env, obj); + } + + // Releases the global reference to the caller. The caller *must* delete the + // global reference when it is done with it. + T Release() { + return static_cast<T>(this->ReleaseInternal()); + } +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_SCOPED_JAVA_REF_H_ diff --git a/chromium/base/android/scoped_java_ref_unittest.cc b/chromium/base/android/scoped_java_ref_unittest.cc new file mode 100644 index 00000000000..36f253c4e99 --- /dev/null +++ b/chromium/base/android/scoped_java_ref_unittest.cc @@ -0,0 +1,122 @@ +// 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. + +#include "base/android/scoped_java_ref.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { +int g_local_refs = 0; +int g_global_refs = 0; + +const JNINativeInterface* g_previous_functions; + +jobject NewGlobalRef(JNIEnv* env, jobject obj) { + ++g_global_refs; + return g_previous_functions->NewGlobalRef(env, obj); +} + +void DeleteGlobalRef(JNIEnv* env, jobject obj) { + --g_global_refs; + return g_previous_functions->DeleteGlobalRef(env, obj); +} + +jobject NewLocalRef(JNIEnv* env, jobject obj) { + ++g_local_refs; + return g_previous_functions->NewLocalRef(env, obj); +} + +void DeleteLocalRef(JNIEnv* env, jobject obj) { + --g_local_refs; + return g_previous_functions->DeleteLocalRef(env, obj); +} +} // namespace + +class ScopedJavaRefTest : public testing::Test { + protected: + virtual void SetUp() { + g_local_refs = 0; + g_global_refs = 0; + JNIEnv* env = AttachCurrentThread(); + g_previous_functions = env->functions; + hooked_functions = *g_previous_functions; + env->functions = &hooked_functions; + // We inject our own functions in JNINativeInterface so we can keep track + // of the reference counting ourselves. + hooked_functions.NewGlobalRef = &NewGlobalRef; + hooked_functions.DeleteGlobalRef = &DeleteGlobalRef; + hooked_functions.NewLocalRef = &NewLocalRef; + hooked_functions.DeleteLocalRef = &DeleteLocalRef; + } + + virtual void TearDown() { + JNIEnv* env = AttachCurrentThread(); + env->functions = g_previous_functions; + } + // From JellyBean release, the instance of this struct provided in JNIEnv is + // read-only, so we deep copy it to allow individual functions to be hooked. + JNINativeInterface hooked_functions; +}; + +// The main purpose of this is testing the various conversions compile. +TEST_F(ScopedJavaRefTest, Conversions) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, "string"); + ScopedJavaGlobalRef<jstring> global(str); + { + ScopedJavaGlobalRef<jobject> global_obj(str); + ScopedJavaLocalRef<jobject> local_obj(global); + const JavaRef<jobject>& obj_ref1(str); + const JavaRef<jobject>& obj_ref2(global); + EXPECT_TRUE(env->IsSameObject(obj_ref1.obj(), obj_ref2.obj())); + EXPECT_TRUE(env->IsSameObject(global_obj.obj(), obj_ref2.obj())); + } + global.Reset(str); + const JavaRef<jstring>& str_ref = str; + EXPECT_EQ("string", ConvertJavaStringToUTF8(str_ref)); + str.Reset(); +} + +TEST_F(ScopedJavaRefTest, RefCounts) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> str; + // The ConvertJavaStringToUTF8 below creates a new string that would normally + // return a local ref. We simulate that by starting the g_local_refs count at + // 1. + g_local_refs = 1; + str.Reset(ConvertUTF8ToJavaString(env, "string")); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(0, g_global_refs); + { + ScopedJavaGlobalRef<jstring> global_str(str); + ScopedJavaGlobalRef<jobject> global_obj(global_str); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(2, g_global_refs); + + ScopedJavaLocalRef<jstring> str2(env, str.Release()); + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef<jstring> str3(str2); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + str2.Reset(); + EXPECT_EQ(0, g_local_refs); + global_str.Reset(); + EXPECT_EQ(1, g_global_refs); + ScopedJavaGlobalRef<jobject> global_obj2(global_obj); + EXPECT_EQ(2, g_global_refs); + } + + EXPECT_EQ(0, g_local_refs); + EXPECT_EQ(0, g_global_refs); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/sys_utils.cc b/chromium/base/android/sys_utils.cc new file mode 100644 index 00000000000..c23125390da --- /dev/null +++ b/chromium/base/android/sys_utils.cc @@ -0,0 +1,31 @@ +// 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/sys_utils.h" + +#include "base/sys_info.h" +#include "jni/SysUtils_jni.h" + +const int64 kLowEndMemoryThreshold = 1024 * 1024 * 512; // 512 mb. + +// Defined and called by JNI +static jboolean IsLowEndDevice(JNIEnv* env, jclass clazz) { + return base::android::SysUtils::IsLowEndDevice(); +} + +namespace base { +namespace android { + +bool SysUtils::Register(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +bool SysUtils::IsLowEndDevice() { + return SysInfo::AmountOfPhysicalMemory() <= kLowEndMemoryThreshold; +} + +SysUtils::SysUtils() { } + +} // namespace android +} // namespace base diff --git a/chromium/base/android/sys_utils.h b/chromium/base/android/sys_utils.h new file mode 100644 index 00000000000..78122ff572d --- /dev/null +++ b/chromium/base/android/sys_utils.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef BASE_ANDROID_SYS_UTILS_H_ +#define BASE_ANDROID_SYS_UTILS_H_ + +#include "base/android/jni_android.h" + +namespace base { +namespace android { + +class BASE_EXPORT SysUtils { + public: + static bool Register(JNIEnv* env); + + static bool IsLowEndDevice(); + + private: + SysUtils(); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_SYS_UTILS_H_ diff --git a/chromium/base/android/thread_utils.h b/chromium/base/android/thread_utils.h new file mode 100644 index 00000000000..cbe65f08134 --- /dev/null +++ b/chromium/base/android/thread_utils.h @@ -0,0 +1,16 @@ +// 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. + +#ifndef BASE_ANDROID_THREAD_UTILS_H_ +#define BASE_ANDROID_THREAD_UTILS_H_ + +#include "base/android/jni_android.h" + +namespace base { + +bool RegisterThreadUtils(JNIEnv* env); + +} // namespace base + +#endif // BASE_ANDROID_THREAD_UTILS_H_ |