diff options
author | pliard@chromium.org <pliard@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-03 16:29:09 +0000 |
---|---|---|
committer | pliard@chromium.org <pliard@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-03 16:29:09 +0000 |
commit | c559db20c909bae7749e98e14e4154f7f8e31638 (patch) | |
tree | 5088678cf4fa849d6a1c1e1b6c82e5b9039f3595 /base | |
parent | b1ac652d9bdb89b22185a4a1dea206b8ca9d1b17 (diff) | |
download | chromium_src-c559db20c909bae7749e98e14e4154f7f8e31638.zip chromium_src-c559db20c909bae7749e98e14e4154f7f8e31638.tar.gz chromium_src-c559db20c909bae7749e98e14e4154f7f8e31638.tar.bz2 |
Add base/android/activity_status.cc
This patch adds a small thread-safe wrapper around the Java
org.chromium.base.ActivityStatus that can be used to listen to activity state
changes from native code.
This uses a base::ObserverListThreadSafe to guarantee that listeners can be
registered on any threads, and that their OnActivityStateChange() method will
always be called on the thread that created them.
+ Ensure both C++ and Java always use the same constants when it comes to
ActivityState values.
TBR=digit@chromium.org
BUG=233536
Review URL: https://codereview.chromium.org/14767011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@198124 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/android/activity_state_list.h | 16 | ||||
-rw-r--r-- | base/android/activity_status.cc | 66 | ||||
-rw-r--r-- | base/android/activity_status.h | 97 | ||||
-rw-r--r-- | base/android/activity_status_unittest.cc | 129 | ||||
-rw-r--r-- | base/android/base_jni_registrar.cc | 4 | ||||
-rw-r--r-- | base/android/java/src/org/chromium/base/ActivityState.template | 14 | ||||
-rw-r--r-- | base/android/java/src/org/chromium/base/ActivityStatus.java | 45 | ||||
-rw-r--r-- | base/base.gyp | 21 | ||||
-rw-r--r-- | base/base.gypi | 2 |
9 files changed, 386 insertions, 8 deletions
diff --git a/base/android/activity_state_list.h b/base/android/activity_state_list.h new file mode 100644 index 0000000..d2a84dc --- /dev/null +++ b/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. + +#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/base/android/activity_status.cc b/base/android/activity_status.cc new file mode 100644 index 0000000..4d0be32 --- /dev/null +++ b/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/base/android/activity_status.h b/base/android/activity_status.h new file mode 100644 index 0000000..554340c --- /dev/null +++ b/base/android/activity_status.h @@ -0,0 +1,97 @@ +// 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/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 +// +BASE_EXPORT class 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/base/android/activity_status_unittest.cc b/base/android/activity_status_unittest.cc new file mode 100644 index 0000000..aa6a69f --- /dev/null +++ b/base/android/activity_status_unittest.cc @@ -0,0 +1,129 @@ +// 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_(), + listener_(NULL) { + } + + 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/base/android/base_jni_registrar.cc b/base/android/base_jni_registrar.cc index 882938b..606ab38 100644 --- a/base/android/base_jni_registrar.cc +++ b/base/android/base_jni_registrar.cc @@ -4,7 +4,7 @@ #include "base/android/base_jni_registrar.h" -#include "base/basictypes.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" @@ -13,6 +13,7 @@ #include "base/android/path_service_android.h" #include "base/android/path_utils.h" #include "base/android/thread_utils.h" +#include "base/basictypes.h" #include "base/debug/trace_event.h" #include "base/message_pump_android.h" #include "base/power_monitor/power_monitor_android.h" @@ -21,6 +22,7 @@ namespace base { namespace android { static RegistrationMethod kBaseRegisteredMethods[] = { + { "ActivityStatus", base::android::ActivityStatus::RegisterBindings }, { "BuildInfo", base::android::BuildInfo::RegisterBindings }, { "CpuFeatures", base::android::RegisterCpuFeatures }, { "ImportantFileWriterAndroid", diff --git a/base/android/java/src/org/chromium/base/ActivityState.template b/base/android/java/src/org/chromium/base/ActivityState.template new file mode 100644 index 0000000..adf990a --- /dev/null +++ b/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/base/android/java/src/org/chromium/base/ActivityStatus.java b/base/android/java/src/org/chromium/base/ActivityStatus.java index 7d0ee94..4747234 100644 --- a/base/android/java/src/org/chromium/base/ActivityStatus.java +++ b/base/android/java/src/org/chromium/base/ActivityStatus.java @@ -5,20 +5,25 @@ package org.chromium.base; import android.app.Activity; +import android.os.Handler; import android.os.Looper; /** - * Provides information about the parent activity's status. + * 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 - public static final int CREATED = 1; - public static final int STARTED = 2; - public static final int RESUMED = 3; - public static final int PAUSED = 4; - public static final int STOPPED = 5; - public static final int DESTROYED = 6; + // 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; @@ -102,4 +107,30 @@ public class ActivityStatus { 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/base/base.gyp b/base/base.gyp index 44ff1d2..73b0793 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -441,6 +441,7 @@ 'type': '<(gtest_target_type)', 'sources': [ # Tests. + 'android/activity_status_unittest.cc', 'android/jni_android_unittest.cc', 'android/jni_array_unittest.cc', 'android/jni_string_unittest.cc', @@ -1124,6 +1125,7 @@ 'target_name': 'base_jni_headers', 'type': 'none', 'sources': [ + 'android/java/src/org/chromium/base/ActivityStatus.java', 'android/java/src/org/chromium/base/BuildInfo.java', 'android/java/src/org/chromium/base/CpuFeatures.java', 'android/java/src/org/chromium/base/ImportantFileWriterAndroid.java', @@ -1144,6 +1146,9 @@ 'variables': { 'java_in_dir': '../base/android/java', }, + 'dependencies': [ + 'base_java_activity_state', + ], 'includes': [ '../build/java.gypi' ], 'conditions': [ ['android_webview_build==0', { @@ -1154,6 +1159,22 @@ ], }, { + 'target_name': 'base_java_activity_state', + 'type': 'none', + # This target is used to auto-generate ActivityState.java + # from a template file. The source file contains a list of + # Java constant declarations matching the ones in + # android/activity_state_list.h. + 'sources': [ + 'android/java/src/org/chromium/base/ActivityState.template', + ], + 'variables': { + 'package_name': 'org/chromium/base', + 'template_deps': ['android/activity_state_list.h'], + }, + 'includes': [ '../build/android/java_cpp_template.gypi' ], + }, + { 'target_name': 'base_java_test_support', 'type': 'none', 'dependencies': [ diff --git a/base/base.gypi b/base/base.gypi index de000d1..f74c619 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -33,6 +33,8 @@ 'allocator/allocator_extension.h', 'allocator/type_profiler_control.cc', 'allocator/type_profiler_control.h', + 'android/activity_status.cc', + 'android/activity_status.h', 'android/base_jni_registrar.cc', 'android/base_jni_registrar.h', 'android/build_info.cc', |