michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/Hal.h" michael@0: #include "mozilla/HalSensor.h" michael@0: michael@0: #include "nsDeviceSensors.h" michael@0: michael@0: #include "nsAutoPtr.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "GeneratedEvents.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "mozilla/dom/DeviceLightEvent.h" michael@0: #include "mozilla/dom/DeviceProximityEvent.h" michael@0: #include "mozilla/dom/UserProximityEvent.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace hal; michael@0: michael@0: #undef near michael@0: michael@0: // also see sDefaultSensorHint in mobile/android/base/GeckoAppShell.java michael@0: #define DEFAULT_SENSOR_POLL 100 michael@0: michael@0: static const nsTArray::index_type NoIndex = michael@0: nsTArray::NoIndex; michael@0: michael@0: class nsDeviceSensorData MOZ_FINAL : public nsIDeviceSensorData michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIDEVICESENSORDATA michael@0: michael@0: nsDeviceSensorData(unsigned long type, double x, double y, double z); michael@0: michael@0: private: michael@0: ~nsDeviceSensorData(); michael@0: michael@0: protected: michael@0: unsigned long mType; michael@0: double mX, mY, mZ; michael@0: }; michael@0: michael@0: nsDeviceSensorData::nsDeviceSensorData(unsigned long type, double x, double y, double z) michael@0: : mType(type), mX(x), mY(y), mZ(z) michael@0: { michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsDeviceSensorData) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDeviceSensorData) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_ADDREF(nsDeviceSensorData) michael@0: NS_IMPL_RELEASE(nsDeviceSensorData) michael@0: michael@0: nsDeviceSensorData::~nsDeviceSensorData() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDeviceSensorData::GetType(uint32_t *aType) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aType); michael@0: *aType = mType; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDeviceSensorData::GetX(double *aX) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aX); michael@0: *aX = mX; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDeviceSensorData::GetY(double *aY) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aY); michael@0: *aY = mY; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDeviceSensorData::GetZ(double *aZ) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aZ); michael@0: *aZ = mZ; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsDeviceSensors, nsIDeviceSensors) michael@0: michael@0: nsDeviceSensors::nsDeviceSensors() michael@0: { michael@0: mIsUserProximityNear = false; michael@0: mLastDOMMotionEventTime = TimeStamp::Now(); michael@0: mEnabled = Preferences::GetBool("device.sensors.enabled", true); michael@0: michael@0: for (int i = 0; i < NUM_SENSOR_TYPE; i++) { michael@0: nsTArray *windows = new nsTArray(); michael@0: mWindowListeners.AppendElement(windows); michael@0: } michael@0: michael@0: mLastDOMMotionEventTime = TimeStamp::Now(); michael@0: } michael@0: michael@0: nsDeviceSensors::~nsDeviceSensors() michael@0: { michael@0: for (int i = 0; i < NUM_SENSOR_TYPE; i++) { michael@0: if (IsSensorEnabled(i)) michael@0: UnregisterSensorObserver((SensorType)i, this); michael@0: } michael@0: michael@0: for (int i = 0; i < NUM_SENSOR_TYPE; i++) { michael@0: delete mWindowListeners[i]; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDeviceSensors::HasWindowListener(uint32_t aType, nsIDOMWindow *aWindow, bool *aRetVal) michael@0: { michael@0: if (!mEnabled) michael@0: *aRetVal = false; michael@0: else michael@0: *aRetVal = mWindowListeners[aType]->IndexOf(aWindow) != NoIndex; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDeviceSensors::AddWindowListener(uint32_t aType, nsIDOMWindow *aWindow) michael@0: { michael@0: if (!mEnabled) michael@0: return NS_OK; michael@0: michael@0: if (mWindowListeners[aType]->IndexOf(aWindow) != NoIndex) michael@0: return NS_OK; michael@0: michael@0: if (!IsSensorEnabled(aType)) { michael@0: RegisterSensorObserver((SensorType)aType, this); michael@0: } michael@0: michael@0: mWindowListeners[aType]->AppendElement(aWindow); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDeviceSensors::RemoveWindowListener(uint32_t aType, nsIDOMWindow *aWindow) michael@0: { michael@0: if (mWindowListeners[aType]->IndexOf(aWindow) == NoIndex) michael@0: return NS_OK; michael@0: michael@0: mWindowListeners[aType]->RemoveElement(aWindow); michael@0: michael@0: if (mWindowListeners[aType]->Length() == 0) michael@0: UnregisterSensorObserver((SensorType)aType, this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDeviceSensors::RemoveWindowAsListener(nsIDOMWindow *aWindow) michael@0: { michael@0: for (int i = 0; i < NUM_SENSOR_TYPE; i++) { michael@0: RemoveWindowListener((SensorType)i, aWindow); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: WindowCannotReceiveSensorEvent (nsPIDOMWindow* aWindow) michael@0: { michael@0: // Check to see if this window is in the background. If michael@0: // it is and it does not have the "background-sensors" permission, michael@0: // don't send any device motion events to it. michael@0: if (!aWindow || !aWindow->IsCurrentInnerWindow()) { michael@0: return true; michael@0: } michael@0: michael@0: if (aWindow->GetOuterWindow()->IsBackground()) { michael@0: nsCOMPtr permMgr = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: NS_ENSURE_TRUE(permMgr, false); michael@0: uint32_t permission = nsIPermissionManager::DENY_ACTION; michael@0: permMgr->TestPermissionFromWindow(aWindow, "background-sensors", &permission); michael@0: return permission != nsIPermissionManager::ALLOW_ACTION; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsDeviceSensors::Notify(const mozilla::hal::SensorData& aSensorData) michael@0: { michael@0: uint32_t type = aSensorData.sensor(); michael@0: michael@0: const InfallibleTArray& values = aSensorData.values(); michael@0: size_t len = values.Length(); michael@0: double x = len > 0 ? values[0] : 0.0; michael@0: double y = len > 1 ? values[1] : 0.0; michael@0: double z = len > 2 ? values[2] : 0.0; michael@0: michael@0: nsCOMArray windowListeners; michael@0: for (uint32_t i = 0; i < mWindowListeners[type]->Length(); i++) { michael@0: windowListeners.AppendObject(mWindowListeners[type]->SafeElementAt(i)); michael@0: } michael@0: michael@0: for (uint32_t i = windowListeners.Count(); i > 0 ; ) { michael@0: --i; michael@0: michael@0: nsCOMPtr pwindow = do_QueryInterface(windowListeners[i]); michael@0: if (WindowCannotReceiveSensorEvent(pwindow)) { michael@0: continue; michael@0: } michael@0: michael@0: nsCOMPtr domdoc; michael@0: windowListeners[i]->GetDocument(getter_AddRefs(domdoc)); michael@0: michael@0: if (domdoc) { michael@0: nsCOMPtr target = do_QueryInterface(windowListeners[i]); michael@0: if (type == nsIDeviceSensorData::TYPE_ACCELERATION || michael@0: type == nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION || michael@0: type == nsIDeviceSensorData::TYPE_GYROSCOPE) michael@0: FireDOMMotionEvent(domdoc, target, type, x, y, z); michael@0: else if (type == nsIDeviceSensorData::TYPE_ORIENTATION) michael@0: FireDOMOrientationEvent(domdoc, target, x, y, z); michael@0: else if (type == nsIDeviceSensorData::TYPE_PROXIMITY) michael@0: FireDOMProximityEvent(target, x, y, z); michael@0: else if (type == nsIDeviceSensorData::TYPE_LIGHT) michael@0: FireDOMLightEvent(target, x); michael@0: michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsDeviceSensors::FireDOMLightEvent(mozilla::dom::EventTarget* aTarget, michael@0: double aValue) michael@0: { michael@0: DeviceLightEventInit init; michael@0: init.mBubbles = true; michael@0: init.mCancelable = false; michael@0: init.mValue = aValue; michael@0: nsRefPtr event = michael@0: DeviceLightEvent::Constructor(aTarget, NS_LITERAL_STRING("devicelight"), init); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: bool defaultActionEnabled; michael@0: aTarget->DispatchEvent(event, &defaultActionEnabled); michael@0: } michael@0: michael@0: void michael@0: nsDeviceSensors::FireDOMProximityEvent(mozilla::dom::EventTarget* aTarget, michael@0: double aValue, michael@0: double aMin, michael@0: double aMax) michael@0: { michael@0: DeviceProximityEventInit init; michael@0: init.mBubbles = true; michael@0: init.mCancelable = false; michael@0: init.mValue = aValue; michael@0: init.mMin = aMin; michael@0: init.mMax = aMax; michael@0: nsRefPtr event = michael@0: DeviceProximityEvent::Constructor(aTarget, michael@0: NS_LITERAL_STRING("deviceproximity"), michael@0: init); michael@0: event->SetTrusted(true); michael@0: michael@0: bool defaultActionEnabled; michael@0: aTarget->DispatchEvent(event, &defaultActionEnabled); michael@0: michael@0: // Some proximity sensors only support a binary near or michael@0: // far measurement. In this case, the sensor should report michael@0: // its maximum range value in the far state and a lesser michael@0: // value in the near state. michael@0: michael@0: bool near = (aValue < aMax); michael@0: if (mIsUserProximityNear != near) { michael@0: mIsUserProximityNear = near; michael@0: FireDOMUserProximityEvent(aTarget, mIsUserProximityNear); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsDeviceSensors::FireDOMUserProximityEvent(mozilla::dom::EventTarget* aTarget, michael@0: bool aNear) michael@0: { michael@0: UserProximityEventInit init; michael@0: init.mBubbles = true; michael@0: init.mCancelable = false; michael@0: init.mNear = aNear; michael@0: nsRefPtr event = michael@0: UserProximityEvent::Constructor(aTarget, michael@0: NS_LITERAL_STRING("userproximity"), michael@0: init); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: bool defaultActionEnabled; michael@0: aTarget->DispatchEvent(event, &defaultActionEnabled); michael@0: } michael@0: michael@0: void michael@0: nsDeviceSensors::FireDOMOrientationEvent(nsIDOMDocument* domdoc, michael@0: EventTarget* target, michael@0: double alpha, michael@0: double beta, michael@0: double gamma) michael@0: { michael@0: nsCOMPtr event; michael@0: bool defaultActionEnabled = true; michael@0: domdoc->CreateEvent(NS_LITERAL_STRING("DeviceOrientationEvent"), getter_AddRefs(event)); michael@0: michael@0: nsCOMPtr oe = do_QueryInterface(event); michael@0: michael@0: if (!oe) { michael@0: return; michael@0: } michael@0: michael@0: oe->InitDeviceOrientationEvent(NS_LITERAL_STRING("deviceorientation"), michael@0: true, michael@0: false, michael@0: alpha, michael@0: beta, michael@0: gamma, michael@0: true); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: target->DispatchEvent(event, &defaultActionEnabled); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsDeviceSensors::FireDOMMotionEvent(nsIDOMDocument *domdoc, michael@0: EventTarget* target, michael@0: uint32_t type, michael@0: double x, michael@0: double y, michael@0: double z) michael@0: { michael@0: // Attempt to coalesce events michael@0: bool fireEvent = TimeStamp::Now() > mLastDOMMotionEventTime + TimeDuration::FromMilliseconds(DEFAULT_SENSOR_POLL); michael@0: michael@0: switch (type) { michael@0: case nsIDeviceSensorData::TYPE_LINEAR_ACCELERATION: michael@0: if (mLastAcceleration.empty()) { michael@0: mLastAcceleration.construct(); michael@0: } michael@0: mLastAcceleration.ref().mX.SetValue(x); michael@0: mLastAcceleration.ref().mY.SetValue(y); michael@0: mLastAcceleration.ref().mZ.SetValue(z); michael@0: break; michael@0: case nsIDeviceSensorData::TYPE_ACCELERATION: michael@0: if (mLastAccelerationIncluduingGravity.empty()) { michael@0: mLastAccelerationIncluduingGravity.construct(); michael@0: } michael@0: mLastAccelerationIncluduingGravity.ref().mX.SetValue(x); michael@0: mLastAccelerationIncluduingGravity.ref().mY.SetValue(y); michael@0: mLastAccelerationIncluduingGravity.ref().mZ.SetValue(z); michael@0: break; michael@0: case nsIDeviceSensorData::TYPE_GYROSCOPE: michael@0: if (mLastRotationRate.empty()) { michael@0: mLastRotationRate.construct(); michael@0: } michael@0: mLastRotationRate.ref().mAlpha.SetValue(x); michael@0: mLastRotationRate.ref().mBeta.SetValue(y); michael@0: mLastRotationRate.ref().mGamma.SetValue(z); michael@0: break; michael@0: } michael@0: michael@0: if (fireEvent) { michael@0: if (mLastAcceleration.empty()) { michael@0: mLastAcceleration.construct(); michael@0: } michael@0: if (mLastAccelerationIncluduingGravity.empty()) { michael@0: mLastAccelerationIncluduingGravity.construct(); michael@0: } michael@0: if (mLastRotationRate.empty()) { michael@0: mLastRotationRate.construct(); michael@0: } michael@0: } else if (mLastAcceleration.empty() || michael@0: mLastAccelerationIncluduingGravity.empty() || michael@0: mLastRotationRate.empty()) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: domdoc->CreateEvent(NS_LITERAL_STRING("DeviceMotionEvent"), getter_AddRefs(event)); michael@0: michael@0: DeviceMotionEvent* me = static_cast(event.get()); michael@0: michael@0: ErrorResult rv; michael@0: me->InitDeviceMotionEvent(NS_LITERAL_STRING("devicemotion"), michael@0: true, michael@0: false, michael@0: mLastAcceleration.ref(), michael@0: mLastAccelerationIncluduingGravity.ref(), michael@0: mLastRotationRate.ref(), michael@0: Nullable(DEFAULT_SENSOR_POLL), michael@0: rv); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: bool defaultActionEnabled = true; michael@0: target->DispatchEvent(event, &defaultActionEnabled); michael@0: michael@0: mLastRotationRate.destroy(); michael@0: mLastAccelerationIncluduingGravity.destroy(); michael@0: mLastAcceleration.destroy(); michael@0: mLastDOMMotionEventTime = TimeStamp::Now(); michael@0: }