diff options
11 files changed, 473 insertions, 11 deletions
diff --git a/base/android/java/org/chromium/base/WeakContext.java b/base/android/java/org/chromium/base/WeakContext.java new file mode 100644 index 0000000..d660cc9 --- /dev/null +++ b/base/android/java/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/base/android/jni_generator/jni_generator_tests.py b/base/android/jni_generator/jni_generator_tests.py index a6c4e0a..159c34d 100755 --- a/base/android/jni_generator/jni_generator_tests.py +++ b/base/android/jni_generator/jni_generator_tests.py @@ -77,7 +77,7 @@ class TestGenerator(unittest.TestCase): String[] projection, String selection, String[] selectionArgs, String sortOrder); private native void nativeGotOrientation( - int nativePtr /* device_orientation::DeviceOrientationAndroid */, + int nativePtr /* device_orientation::DataFetcherImplAndroid */, double alpha, double beta, double gamma); """ natives = jni_generator.ExtractNatives(test_data) @@ -189,7 +189,7 @@ class TestGenerator(unittest.TestCase): name='GotOrientation', params=[Param(datatype='int', cpp_class_name= - 'device_orientation::DeviceOrientationAndroid', + 'device_orientation::DataFetcherImplAndroid', name='nativePtr'), Param(datatype='double', name='alpha'), @@ -200,7 +200,7 @@ class TestGenerator(unittest.TestCase): ], java_class_name='', type='method', - p0_type='device_orientation::DeviceOrientationAndroid'), + p0_type='device_orientation::DataFetcherImplAndroid'), ] self.assertListEquals(golden_natives, natives) h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', @@ -322,8 +322,8 @@ static void GotOrientation(JNIEnv* env, jobject obj, jdouble beta, jdouble gamma) { DCHECK(nativePtr) << "GotOrientation"; - device_orientation::DeviceOrientationAndroid* native = - reinterpret_cast<device_orientation::DeviceOrientationAndroid*>(nativePtr); + device_orientation::DataFetcherImplAndroid* native = + reinterpret_cast<device_orientation::DataFetcherImplAndroid*>(nativePtr); return native->GotOrientation(env, obj, alpha, beta, gamma); } diff --git a/content/browser/device_orientation/data_fetcher.h b/content/browser/device_orientation/data_fetcher.h index 6f6bd99..8e69f1f 100644 --- a/content/browser/device_orientation/data_fetcher.h +++ b/content/browser/device_orientation/data_fetcher.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -12,6 +12,10 @@ class Orientation; class DataFetcher { public: virtual ~DataFetcher() {} + + // Returns false if there was a fatal error getting the orientation. + // Returns true otherwise. If the fetcher has orientation data available + // it will fill it in, otherwise the argument will be unaltered. virtual bool GetOrientation(Orientation*) = 0; }; diff --git a/content/browser/device_orientation/data_fetcher_impl_android.cc b/content/browser/device_orientation/data_fetcher_impl_android.cc new file mode 100644 index 0000000..6f54192 --- /dev/null +++ b/content/browser/device_orientation/data_fetcher_impl_android.cc @@ -0,0 +1,90 @@ +// 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 "content/browser/device_orientation/data_fetcher_impl_android.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "content/browser/device_orientation/orientation.h" +#include "jni/device_orientation_jni.h" + +using base::android::AttachCurrentThread; +using base::android::CheckException; +using base::android::GetClass; +using base::android::GetMethodID; +using base::android::ScopedJavaGlobalRef; +using base::android::ScopedJavaLocalRef; + +namespace device_orientation { + +namespace { + +// This should match ProviderImpl::kDesiredSamplingIntervalMs. +// TODO(husky): Make that constant public so we can use it directly. +const int kPeriodInMilliseconds = 100; + +base::LazyInstance<ScopedJavaGlobalRef<jobject> > + g_jni_obj = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +DataFetcherImplAndroid::DataFetcherImplAndroid() { +} + +void DataFetcherImplAndroid::Init(JNIEnv* env) { + bool result = RegisterNativesImpl(env); + DCHECK(result); + + g_jni_obj.Get().Reset(Java_DeviceOrientation_create(env)); +} + +DataFetcher* DataFetcherImplAndroid::Create() { + scoped_ptr<DataFetcherImplAndroid> fetcher(new DataFetcherImplAndroid); + if (fetcher->Start(kPeriodInMilliseconds)) + return fetcher.release(); + + LOG(ERROR) << "DataFetcherImplAndroid::Start failed!"; + return NULL; +} + +DataFetcherImplAndroid::~DataFetcherImplAndroid() { + Stop(); +} + +bool DataFetcherImplAndroid::GetOrientation(Orientation* orientation) { + // Do we have a new orientation value? (It's safe to do this outside the lock + // because we only skip the lock if the value is null. We always enter the + // lock if we're going to make use of the new value.) + if (next_orientation_.get()) { + base::AutoLock autolock(next_orientation_lock_); + next_orientation_.swap(current_orientation_); + } + if (current_orientation_.get()) + *orientation = *current_orientation_; + return true; +} + +void DataFetcherImplAndroid::GotOrientation( + JNIEnv*, jobject, double alpha, double beta, double gamma) { + base::AutoLock autolock(next_orientation_lock_); + next_orientation_.reset( + new Orientation(true, alpha, true, beta, true, gamma, true, true)); +} + +bool DataFetcherImplAndroid::Start(int rate_in_milliseconds) { + DCHECK(!g_jni_obj.Get().is_null()); + return Java_DeviceOrientation_start(AttachCurrentThread(), + g_jni_obj.Get().obj(), + reinterpret_cast<jint>(this), + rate_in_milliseconds); +} + +void DataFetcherImplAndroid::Stop() { + DCHECK(!g_jni_obj.Get().is_null()); + Java_DeviceOrientation_stop(AttachCurrentThread(), g_jni_obj.Get().obj()); +} + +} // namespace device_orientation diff --git a/content/browser/device_orientation/data_fetcher_impl_android.h b/content/browser/device_orientation/data_fetcher_impl_android.h new file mode 100644 index 0000000..e14aa10 --- /dev/null +++ b/content/browser/device_orientation/data_fetcher_impl_android.h @@ -0,0 +1,63 @@ +// 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 CHROME_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_IMPL_ANDROID_H_ +#define CHROME_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_IMPL_ANDROID_H_ +#pragma once + +#include <jni.h> + +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "content/browser/device_orientation/data_fetcher.h" +#include "content/browser/device_orientation/orientation.h" + +namespace device_orientation { + +// Android implementation of DeviceOrientation API. + +// Android's SensorManager has a push API, whereas Chrome wants to pull data. +// To fit them together, we store incoming sensor events in a 1-element buffer. +// SensorManager calls SetOrientation() which pushes a new value (discarding the +// previous value if any). Chrome calls GetOrientation() which reads the most +// recent value. Repeated calls to GetOrientation() will return the same value. + +class DataFetcherImplAndroid : public DataFetcher { + public: + // Must be called at startup, before Create(). + static void Init(JNIEnv* env); + + // Factory function. We'll listen for events for the lifetime of this object. + // Returns NULL on error. + static DataFetcher* Create(); + + virtual ~DataFetcherImplAndroid(); + + // Called from Java via JNI. + void GotOrientation(JNIEnv*, jobject, + double alpha, double beta, double gamma); + + // Implementation of DataFetcher. + virtual bool GetOrientation(Orientation* orientation) OVERRIDE; + + private: + DataFetcherImplAndroid(); + + // Wrappers for JNI methods. + bool Start(int rateInMilliseconds); + void Stop(); + + // Value returned by GetOrientation. + scoped_ptr<Orientation> current_orientation_; + + // 1-element buffer, written by GotOrientation, read by GetOrientation. + base::Lock next_orientation_lock_; + scoped_ptr<Orientation> next_orientation_; + + DISALLOW_COPY_AND_ASSIGN(DataFetcherImplAndroid); +}; + +} // namespace device_orientation + +#endif // CHROME_BROWSER_DEVICE_ORIENTATION_DATA_FETCHER_IMPL_ANDROID_H_ diff --git a/content/browser/device_orientation/provider.cc b/content/browser/device_orientation/provider.cc index 1287b0e..659956a 100644 --- a/content/browser/device_orientation/provider.cc +++ b/content/browser/device_orientation/provider.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -11,6 +11,8 @@ #if defined(OS_MACOSX) #include "content/browser/device_orientation/accelerometer_mac.h" +#elif defined(OS_ANDROID) +#include "content/browser/device_orientation/data_fetcher_impl_android.h" #endif using content::BrowserThread; @@ -23,6 +25,8 @@ Provider* Provider::GetInstance() { const ProviderImpl::DataFetcherFactory default_factories[] = { #if defined(OS_MACOSX) AccelerometerMac::Create, +#elif defined(OS_ANDROID) + DataFetcherImplAndroid::Create, #endif NULL }; diff --git a/content/browser/device_orientation/provider_impl.cc b/content/browser/device_orientation/provider_impl.cc index 537d25b..b27421c 100644 --- a/content/browser/device_orientation/provider_impl.cc +++ b/content/browser/device_orientation/provider_impl.cc @@ -83,7 +83,8 @@ void ProviderImpl::DoInitializePollingThread( last_orientation_ = orientation; // Notify observers. - ScheduleDoNotify(orientation); + if (!orientation.IsEmpty()) + ScheduleDoNotify(orientation); // Start polling. ScheduleDoPoll(); @@ -139,7 +140,8 @@ void ProviderImpl::DoPoll() { return; } - if (SignificantlyDifferent(orientation, last_orientation_)) { + if (!orientation.IsEmpty() && + SignificantlyDifferent(orientation, last_orientation_)) { last_orientation_ = orientation; ScheduleDoNotify(orientation); } diff --git a/content/browser/device_orientation/provider_unittest.cc b/content/browser/device_orientation/provider_unittest.cc index e95e7b8..17d3d43 100644 --- a/content/browser/device_orientation/provider_unittest.cc +++ b/content/browser/device_orientation/provider_unittest.cc @@ -68,7 +68,8 @@ class UpdateChecker : public Provider::Observer { // Class for injecting test orientation data into the Provider. class MockOrientationFactory : public base::RefCounted<MockOrientationFactory> { public: - MockOrientationFactory() { + MockOrientationFactory() + : is_failing_(false) { EXPECT_FALSE(instance_); instance_ = this; } @@ -83,6 +84,11 @@ class MockOrientationFactory : public base::RefCounted<MockOrientationFactory> { orientation_ = orientation; } + void SetFailing(bool is_failing) { + base::AutoLock auto_lock(lock_); + is_failing_ = is_failing; + } + private: friend class base::RefCounted<MockOrientationFactory>; @@ -99,6 +105,8 @@ class MockOrientationFactory : public base::RefCounted<MockOrientationFactory> { // From DataFetcher. Called by the Provider. virtual bool GetOrientation(Orientation* orientation) { base::AutoLock auto_lock(orientation_factory_->lock_); + if (orientation_factory_->is_failing_) + return false; *orientation = orientation_factory_->orientation_; return true; } @@ -109,6 +117,7 @@ class MockOrientationFactory : public base::RefCounted<MockOrientationFactory> { static MockOrientationFactory* instance_; Orientation orientation_; + bool is_failing_; base::Lock lock_; }; @@ -303,7 +312,7 @@ TEST_F(DeviceOrientationProviderTest, MAYBE_StartFailing) { MessageLoop::current()->Run(); checker_a->AddExpectation(Orientation::Empty()); - orientation_factory->SetOrientation(Orientation::Empty()); + orientation_factory->SetFailing(true); MessageLoop::current()->Run(); checker_b->AddExpectation(Orientation::Empty()); diff --git a/content/content_browser.gypi b/content/content_browser.gypi index a6df267..77488ba 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -243,6 +243,8 @@ 'browser/device_orientation/accelerometer_mac.cc', 'browser/device_orientation/accelerometer_mac.h', 'browser/device_orientation/data_fetcher.h', + 'browser/device_orientation/data_fetcher_impl_android.cc', + 'browser/device_orientation/data_fetcher_impl_android.h', 'browser/device_orientation/message_filter.cc', 'browser/device_orientation/message_filter.h', 'browser/device_orientation/orientation.h', diff --git a/content/content_jni.gypi b/content/content_jni.gypi index dc6849b..6165422 100644 --- a/content/content_jni.gypi +++ b/content/content_jni.gypi @@ -10,6 +10,7 @@ 'variables': { 'java_sources': [ 'public/android/java/org/chromium/content/browser/CommandLine.java', + 'public/android/java/org/chromium/content/browser/DeviceOrientation.java', 'public/android/java/org/chromium/content/browser/JNIHelper.java', 'public/android/java/org/chromium/content/browser/LibraryLoader.java', 'public/android/java/org/chromium/content/browser/LocationProvider.java', @@ -17,6 +18,7 @@ ], 'jni_headers': [ '<(SHARED_INTERMEDIATE_DIR)/content/jni/command_line_jni.h', + '<(SHARED_INTERMEDIATE_DIR)/content/jni/device_orientation_jni.h', '<(SHARED_INTERMEDIATE_DIR)/content/jni/jni_helper_jni.h', '<(SHARED_INTERMEDIATE_DIR)/content/jni/library_loader_jni.h', '<(SHARED_INTERMEDIATE_DIR)/content/jni/location_provider_jni.h', diff --git a/content/public/android/java/org/chromium/content/browser/DeviceOrientation.java b/content/public/android/java/org/chromium/content/browser/DeviceOrientation.java new file mode 100644 index 0000000..9859e84 --- /dev/null +++ b/content/public/android/java/org/chromium/content/browser/DeviceOrientation.java @@ -0,0 +1,241 @@ +// 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.browser; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.Looper; + +import org.chromium.base.CalledByNative; +import org.chromium.base.WeakContext; + +import java.util.List; + +/** + * Android implementation of the DeviceOrientation API. + */ +class DeviceOrientation implements SensorEventListener { + + // These fields are lazily initialized by getHandler(). + private Thread mThread; + private Handler mHandler; + + // Non-zero if and only if we're listening for events. + // To avoid race conditions on the C++ side, access must be synchronized. + private int mNativePtr; + + // The gravity vector expressed in the body frame. + private float[] mGravityVector; + + // The geomagnetic vector expressed in the body frame. + private float[] mMagneticFieldVector; + + // Lazily initialized when registering for notifications. + private SensorManager mSensorManager; + + private DeviceOrientation() { + } + + /** + * Start listening for sensor events. If this object is already listening + * for events, the old callback is unregistered first. + * + * @param nativePtr Value to pass to nativeGotOrientation() for each event. + * @param rateInMilliseconds Requested callback rate in milliseconds. The + * actual rate may be higher. Unwanted events should be ignored. + * @return True on success. + */ + @CalledByNative + public synchronized boolean start(int nativePtr, int rateInMilliseconds) { + stop(); + if (registerForSensors(rateInMilliseconds)) { + mNativePtr = nativePtr; + return true; + } + return false; + } + + /** + * Stop listening for sensor events. Always succeeds. + * + * We strictly guarantee that nativeGotOrientation() will not be called + * after this method returns. + */ + @CalledByNative + public synchronized void stop() { + if (mNativePtr != 0) { + mNativePtr = 0; + unregisterForSensors(); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Nothing + } + + @Override + public void onSensorChanged(SensorEvent event) { + switch (event.sensor.getType()) { + case Sensor.TYPE_ACCELEROMETER: + if (mGravityVector == null) { + mGravityVector = new float[3]; + } + System.arraycopy(event.values, 0, mGravityVector, 0, mGravityVector.length); + break; + + case Sensor.TYPE_MAGNETIC_FIELD: + if (mMagneticFieldVector == null) { + mMagneticFieldVector = new float[3]; + } + System.arraycopy(event.values, 0, mMagneticFieldVector, 0, + mMagneticFieldVector.length); + break; + + default: + // Unexpected + return; + } + + getOrientationUsingGetRotationMatrix(); + } + + void getOrientationUsingGetRotationMatrix() { + if (mGravityVector == null || mMagneticFieldVector == null) { + return; + } + + // Get the rotation matrix. + // The rotation matrix that transforms from the body frame to the earth + // frame. + float[] deviceRotationMatrix = new float[9]; + if (!SensorManager.getRotationMatrix(deviceRotationMatrix, null, mGravityVector, + mMagneticFieldVector)) { + return; + } + + // Convert rotation matrix to rotation angles. + // Assuming that the rotations are appied in the order listed at + // http://developer.android.com/reference/android/hardware/SensorEvent.html#values + // the rotations are applied about the same axes and in the same order as required by the + // API. The only conversions are sign changes as follows. The angles are in radians + + float[] rotationAngles = new float[3]; + SensorManager.getOrientation(deviceRotationMatrix, rotationAngles); + + double alpha = Math.toDegrees(-rotationAngles[0]) - 90.0; + while (alpha < 0.0) { + alpha += 360.0; // [0, 360) + } + + double beta = Math.toDegrees(-rotationAngles[1]); + while (beta < -180.0) { + beta += 360.0; // [-180, 180) + } + + double gamma = Math.toDegrees(rotationAngles[2]); + while (gamma < -90.0) { + gamma += 360.0; // [-90, 90) + } + + gotOrientation(alpha, beta, gamma); + } + + boolean registerForSensors(int rateInMilliseconds) { + if (registerForSensorType(Sensor.TYPE_ACCELEROMETER, rateInMilliseconds) + && registerForSensorType(Sensor.TYPE_MAGNETIC_FIELD, rateInMilliseconds)) { + return true; + } + unregisterForSensors(); + return false; + } + + private SensorManager getSensorManager() { + if (mSensorManager != null) { + return mSensorManager; + } + mSensorManager = (SensorManager)WeakContext.getSystemService(Context.SENSOR_SERVICE); + return mSensorManager; + } + + void unregisterForSensors() { + SensorManager sensorManager = getSensorManager(); + if (sensorManager == null) { + return; + } + sensorManager.unregisterListener(this); + } + + boolean registerForSensorType(int type, int rateInMilliseconds) { + SensorManager sensorManager = getSensorManager(); + if (sensorManager == null) { + return false; + } + List<Sensor> sensors = sensorManager.getSensorList(type); + if (sensors.isEmpty()) { + return false; + } + + final int rateInMicroseconds = 1000 * rateInMilliseconds; + // We want to err on the side of getting more events than we need. + final int requestedRate = rateInMicroseconds / 2; + + // TODO(steveblock): Consider handling multiple sensors. + return sensorManager.registerListener(this, sensors.get(0), requestedRate, getHandler()); + } + + synchronized void gotOrientation(double alpha, double beta, double gamma) { + if (mNativePtr != 0) { + nativeGotOrientation(mNativePtr, alpha, beta, gamma); + } + } + + private synchronized Handler getHandler() { + // If we don't have a background thread, start it now. + if (mThread == null) { + mThread = new Thread(new Runnable() { + public void run() { + Looper.prepare(); + // Our Handler doesn't actually have to do anything, because + // SensorManager posts directly to the underlying Looper. + setHandler(new Handler()); + Looper.loop(); + } + }); + mThread.start(); + } + // Wait for the background thread to spin up. + while (mHandler == null) { + try { + wait(); + } catch (InterruptedException e) { + // Somebody doesn't want us to wait! That's okay, SensorManager accepts null. + return null; + } + } + return mHandler; + } + + private synchronized void setHandler(Handler handler) { + mHandler = handler; + notify(); + } + + @CalledByNative + private static DeviceOrientation create() { + return new DeviceOrientation(); + } + + /** + * See chrome/browser/device_orientation/data_fetcher_impl_android.cc + */ + private native void nativeGotOrientation( + int nativePtr /* device_orientation::DataFetcherImplAndroid */, + double alpha, double beta, double gamma); +} |