summaryrefslogtreecommitdiffstats
path: root/base
diff options
context:
space:
mode:
authorpliard@chromium.org <pliard@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-03 16:29:09 +0000
committerpliard@chromium.org <pliard@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-03 16:29:09 +0000
commitc559db20c909bae7749e98e14e4154f7f8e31638 (patch)
tree5088678cf4fa849d6a1c1e1b6c82e5b9039f3595 /base
parentb1ac652d9bdb89b22185a4a1dea206b8ca9d1b17 (diff)
downloadchromium_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.h16
-rw-r--r--base/android/activity_status.cc66
-rw-r--r--base/android/activity_status.h97
-rw-r--r--base/android/activity_status_unittest.cc129
-rw-r--r--base/android/base_jni_registrar.cc4
-rw-r--r--base/android/java/src/org/chromium/base/ActivityState.template14
-rw-r--r--base/android/java/src/org/chromium/base/ActivityStatus.java45
-rw-r--r--base/base.gyp21
-rw-r--r--base/base.gypi2
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',