diff options
Diffstat (limited to '')
46 files changed, 5450 insertions, 0 deletions
diff --git a/hal/Hal.cpp b/hal/Hal.cpp new file mode 100644 index 0000000000..c8e25690bc --- /dev/null +++ b/hal/Hal.cpp @@ -0,0 +1,452 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" + +#include "HalImpl.h" +#include "HalLog.h" +#include "HalSandbox.h" +#include "HalWakeLockInternal.h" +#include "mozilla/dom/Document.h" +#include "nsXULAppAPI.h" +#include "nsPIDOMWindow.h" +#include "mozilla/Observer.h" +#include "mozilla/dom/ContentChild.h" +#include "WindowIdentifier.h" + +#ifdef XP_WIN +# include <process.h> +# define getpid _getpid +#endif + +using namespace mozilla::services; +using namespace mozilla::dom; + +#define PROXY_IF_SANDBOXED(_call) \ + do { \ + if (InSandbox()) { \ + if (!hal_sandbox::HalChildDestroyed()) { \ + hal_sandbox::_call; \ + } \ + } else { \ + hal_impl::_call; \ + } \ + } while (0) + +#define RETURN_PROXY_IF_SANDBOXED(_call, defValue) \ + do { \ + if (InSandbox()) { \ + if (hal_sandbox::HalChildDestroyed()) { \ + return defValue; \ + } \ + return hal_sandbox::_call; \ + } else { \ + return hal_impl::_call; \ + } \ + } while (0) + +namespace mozilla::hal { + +static bool sInitialized = false; + +mozilla::LogModule* GetHalLog() { + static mozilla::LazyLogModule sHalLog("hal"); + return sHalLog; +} + +namespace { + +void AssertMainThread() { MOZ_ASSERT(NS_IsMainThread()); } + +bool InSandbox() { return GeckoProcessType_Content == XRE_GetProcessType(); } + +bool WindowIsActive(nsPIDOMWindowInner* aWindow) { + NS_ENSURE_TRUE(aWindow, false); + dom::Document* document = aWindow->GetDoc(); + NS_ENSURE_TRUE(document, false); + return !document->Hidden(); +} + +StaticAutoPtr<WindowIdentifier::IDArrayType> gLastIDToVibrate; + +static void RecordLastIDToVibrate(const WindowIdentifier& aId) { + if (!InSandbox()) { + *gLastIDToVibrate = aId.AsArray().Clone(); + } +} + +static bool MayCancelVibration(const WindowIdentifier& aId) { + // Although only active windows may start vibrations, a window may + // cancel its own vibration even if it's no longer active. + // + // After a window is marked as inactive, it sends a CancelVibrate + // request. We want this request to cancel a playing vibration + // started by that window, so we certainly don't want to reject the + // cancellation request because the window is now inactive. + // + // But it could be the case that, after this window became inactive, + // some other window came along and started a vibration. We don't + // want this window's cancellation request to cancel that window's + // actively-playing vibration! + // + // To solve this problem, we keep track of the id of the last window + // to start a vibration, and only accepts cancellation requests from + // the same window. All other cancellation requests are ignored. + + return InSandbox() || (*gLastIDToVibrate == aId.AsArray()); +} + +} // namespace + +void Vibrate(const nsTArray<uint32_t>& pattern, nsPIDOMWindowInner* window) { + Vibrate(pattern, WindowIdentifier(window)); +} + +void Vibrate(const nsTArray<uint32_t>& pattern, WindowIdentifier&& id) { + AssertMainThread(); + + // Only active windows may start vibrations. If |id| hasn't gone + // through the IPC layer -- that is, if our caller is the outside + // world, not hal_proxy -- check whether the window is active. If + // |id| has gone through IPC, don't check the window's visibility; + // only the window corresponding to the bottommost process has its + // visibility state set correctly. + if (!id.HasTraveledThroughIPC() && !WindowIsActive(id.GetWindow())) { + HAL_LOG("Vibrate: Window is inactive, dropping vibrate."); + return; + } + + RecordLastIDToVibrate(id); + + // Don't forward our ID if we are not in the sandbox, because hal_impl + // doesn't need it, and we don't want it to be tempted to read it. The + // empty identifier will assert if it's used. + PROXY_IF_SANDBOXED( + Vibrate(pattern, InSandbox() ? std::move(id) : WindowIdentifier())); +} + +void CancelVibrate(nsPIDOMWindowInner* window) { + CancelVibrate(WindowIdentifier(window)); +} + +void CancelVibrate(WindowIdentifier&& id) { + AssertMainThread(); + + if (MayCancelVibration(id)) { + // Don't forward our ID if we are not in the sandbox, because hal_impl + // doesn't need it, and we don't want it to be tempted to read it. The + // empty identifier will assert if it's used. + PROXY_IF_SANDBOXED( + CancelVibrate(InSandbox() ? std::move(id) : WindowIdentifier())); + } +} + +template <class InfoType> +class ObserversManager { + public: + void AddObserver(Observer<InfoType>* aObserver) { + mObservers.AddObserver(aObserver); + + if (mObservers.Length() == 1) { + EnableNotifications(); + } + } + + void RemoveObserver(Observer<InfoType>* aObserver) { + bool removed = mObservers.RemoveObserver(aObserver); + if (!removed) { + return; + } + + if (mObservers.Length() == 0) { + DisableNotifications(); + OnNotificationsDisabled(); + } + } + + void BroadcastInformation(const InfoType& aInfo) { + mObservers.Broadcast(aInfo); + } + + protected: + ~ObserversManager() { MOZ_ASSERT(mObservers.Length() == 0); } + + virtual void EnableNotifications() = 0; + virtual void DisableNotifications() = 0; + virtual void OnNotificationsDisabled() {} + + private: + mozilla::ObserverList<InfoType> mObservers; +}; + +template <class InfoType> +class CachingObserversManager : public ObserversManager<InfoType> { + public: + InfoType GetCurrentInformation() { + if (mHasValidCache) { + return mInfo; + } + + GetCurrentInformationInternal(&mInfo); + mHasValidCache = true; + return mInfo; + } + + void CacheInformation(const InfoType& aInfo) { + mHasValidCache = true; + mInfo = aInfo; + } + + void BroadcastCachedInformation() { this->BroadcastInformation(mInfo); } + + protected: + virtual void GetCurrentInformationInternal(InfoType*) = 0; + + void OnNotificationsDisabled() override { mHasValidCache = false; } + + private: + InfoType mInfo; + bool mHasValidCache; +}; + +class BatteryObserversManager final + : public CachingObserversManager<BatteryInformation> { + protected: + void EnableNotifications() override { + PROXY_IF_SANDBOXED(EnableBatteryNotifications()); + } + + void DisableNotifications() override { + PROXY_IF_SANDBOXED(DisableBatteryNotifications()); + } + + void GetCurrentInformationInternal(BatteryInformation* aInfo) override { + PROXY_IF_SANDBOXED(GetCurrentBatteryInformation(aInfo)); + } +}; + +class NetworkObserversManager final + : public CachingObserversManager<NetworkInformation> { + protected: + void EnableNotifications() override { + PROXY_IF_SANDBOXED(EnableNetworkNotifications()); + } + + void DisableNotifications() override { + PROXY_IF_SANDBOXED(DisableNetworkNotifications()); + } + + void GetCurrentInformationInternal(NetworkInformation* aInfo) override { + PROXY_IF_SANDBOXED(GetCurrentNetworkInformation(aInfo)); + } +}; + +class WakeLockObserversManager final + : public ObserversManager<WakeLockInformation> { + protected: + void EnableNotifications() override { + PROXY_IF_SANDBOXED(EnableWakeLockNotifications()); + } + + void DisableNotifications() override { + PROXY_IF_SANDBOXED(DisableWakeLockNotifications()); + } +}; + +typedef mozilla::ObserverList<SensorData> SensorObserverList; +StaticAutoPtr<SensorObserverList> sSensorObservers[NUM_SENSOR_TYPE]; + +static SensorObserverList* GetSensorObservers(SensorType sensor_type) { + AssertMainThread(); + MOZ_ASSERT(sensor_type < NUM_SENSOR_TYPE); + + if (!sSensorObservers[sensor_type]) { + sSensorObservers[sensor_type] = new SensorObserverList(); + } + + return sSensorObservers[sensor_type]; +} + +#define MOZ_IMPL_HAL_OBSERVER(name_) \ + StaticAutoPtr<name_##ObserversManager> s##name_##Observers; \ + \ + static name_##ObserversManager* name_##Observers() { \ + AssertMainThread(); \ + \ + if (!s##name_##Observers) { \ + MOZ_ASSERT(sInitialized); \ + s##name_##Observers = new name_##ObserversManager(); \ + } \ + \ + return s##name_##Observers; \ + } \ + \ + void Register##name_##Observer(name_##Observer* aObserver) { \ + AssertMainThread(); \ + name_##Observers()->AddObserver(aObserver); \ + } \ + \ + void Unregister##name_##Observer(name_##Observer* aObserver) { \ + AssertMainThread(); \ + name_##Observers()->RemoveObserver(aObserver); \ + } + +MOZ_IMPL_HAL_OBSERVER(Battery) + +void GetCurrentBatteryInformation(BatteryInformation* aInfo) { + *aInfo = BatteryObservers()->GetCurrentInformation(); +} + +void NotifyBatteryChange(const BatteryInformation& aInfo) { + BatteryObservers()->CacheInformation(aInfo); + BatteryObservers()->BroadcastCachedInformation(); +} + +void EnableSensorNotifications(SensorType aSensor) { + AssertMainThread(); + PROXY_IF_SANDBOXED(EnableSensorNotifications(aSensor)); +} + +void DisableSensorNotifications(SensorType aSensor) { + AssertMainThread(); + PROXY_IF_SANDBOXED(DisableSensorNotifications(aSensor)); +} + +void RegisterSensorObserver(SensorType aSensor, ISensorObserver* aObserver) { + SensorObserverList* observers = GetSensorObservers(aSensor); + + observers->AddObserver(aObserver); + if (observers->Length() == 1) { + EnableSensorNotifications(aSensor); + } +} + +void UnregisterSensorObserver(SensorType aSensor, ISensorObserver* aObserver) { + SensorObserverList* observers = GetSensorObservers(aSensor); + if (!observers->RemoveObserver(aObserver) || observers->Length() > 0) { + return; + } + DisableSensorNotifications(aSensor); +} + +void NotifySensorChange(const SensorData& aSensorData) { + SensorObserverList* observers = GetSensorObservers(aSensorData.sensor()); + + observers->Broadcast(aSensorData); +} + +MOZ_IMPL_HAL_OBSERVER(Network) + +void GetCurrentNetworkInformation(NetworkInformation* aInfo) { + *aInfo = NetworkObservers()->GetCurrentInformation(); +} + +void NotifyNetworkChange(const NetworkInformation& aInfo) { + NetworkObservers()->CacheInformation(aInfo); + NetworkObservers()->BroadcastCachedInformation(); +} + +MOZ_IMPL_HAL_OBSERVER(WakeLock) + +void ModifyWakeLock(const nsAString& aTopic, WakeLockControl aLockAdjust, + WakeLockControl aHiddenAdjust) { + AssertMainThread(); + + PROXY_IF_SANDBOXED(ModifyWakeLock(aTopic, aLockAdjust, aHiddenAdjust)); +} + +void GetWakeLockInfo(const nsAString& aTopic, + WakeLockInformation* aWakeLockInfo) { + AssertMainThread(); + PROXY_IF_SANDBOXED(GetWakeLockInfo(aTopic, aWakeLockInfo)); +} + +void NotifyWakeLockChange(const WakeLockInformation& aInfo) { + AssertMainThread(); + WakeLockObservers()->BroadcastInformation(aInfo); +} + +RefPtr<GenericNonExclusivePromise> LockScreenOrientation( + const ScreenOrientation& aOrientation) { + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(LockScreenOrientation(aOrientation), nullptr); +} + +void UnlockScreenOrientation() { + AssertMainThread(); + PROXY_IF_SANDBOXED(UnlockScreenOrientation()); +} + +void SetProcessPriority(int aPid, ProcessPriority aPriority) { + // n.b. The sandboxed implementation crashes; SetProcessPriority works only + // from the main process. + PROXY_IF_SANDBOXED(SetProcessPriority(aPid, aPriority)); +} + +// From HalTypes.h. +const char* ProcessPriorityToString(ProcessPriority aPriority) { + switch (aPriority) { + case PROCESS_PRIORITY_PARENT_PROCESS: + return "PARENT_PROCESS"; + case PROCESS_PRIORITY_PREALLOC: + return "PREALLOC"; + case PROCESS_PRIORITY_FOREGROUND_HIGH: + return "FOREGROUND_HIGH"; + case PROCESS_PRIORITY_FOREGROUND: + return "FOREGROUND"; + case PROCESS_PRIORITY_FOREGROUND_KEYBOARD: + return "FOREGROUND_KEYBOARD"; + case PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE: + return "BACKGROUND_PERCEIVABLE"; + case PROCESS_PRIORITY_BACKGROUND: + return "BACKGROUND"; + case PROCESS_PRIORITY_UNKNOWN: + return "UNKNOWN"; + default: + MOZ_ASSERT(false); + return "???"; + } +} + +UniquePtr<hal::PerformanceHintSession> CreatePerformanceHintSession( + const nsTArray<PlatformThreadHandle>& aThreads, + mozilla::TimeDuration aTargetWorkDuration) { + return hal_impl::CreatePerformanceHintSession(aThreads, aTargetWorkDuration); +} + +const Maybe<hal::HeterogeneousCpuInfo>& GetHeterogeneousCpuInfo() { + return hal_impl::GetHeterogeneousCpuInfo(); +} + +void Init() { + MOZ_ASSERT(!sInitialized); + + if (!InSandbox()) { + gLastIDToVibrate = new WindowIdentifier::IDArrayType(); + } + + WakeLockInit(); + + sInitialized = true; +} + +void Shutdown() { + MOZ_ASSERT(sInitialized); + + gLastIDToVibrate = nullptr; + + sBatteryObservers = nullptr; + sNetworkObservers = nullptr; + sWakeLockObservers = nullptr; + + for (auto& sensorObserver : sSensorObservers) { + sensorObserver = nullptr; + } + + sInitialized = false; +} + +} // namespace mozilla::hal diff --git a/hal/Hal.h b/hal/Hal.h new file mode 100644 index 0000000000..705dadff18 --- /dev/null +++ b/hal/Hal.h @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_Hal_h +#define mozilla_Hal_h + +#include "base/platform_thread.h" +#include "nsTArray.h" +#include "mozilla/hal_sandbox/PHal.h" +#include "mozilla/HalScreenConfiguration.h" +#include "mozilla/HalBatteryInformation.h" +#include "mozilla/HalNetworkInformation.h" +#include "mozilla/HalWakeLockInformation.h" +#include "mozilla/HalTypes.h" +#include "mozilla/MozPromise.h" + +#include <cstdint> + +/* + * Hal.h contains the public Hal API. + * + * By default, this file defines its functions in the hal namespace, but if + * MOZ_HAL_NAMESPACE is defined, we'll define our functions in that namespace. + * + * This is used by HalImpl.h and HalSandbox.h, which define copies of all the + * functions here in the hal_impl and hal_sandbox namespaces. + */ + +class nsPIDOMWindowInner; + +#ifndef MOZ_HAL_NAMESPACE +# define MOZ_HAL_NAMESPACE hal +# define MOZ_DEFINED_HAL_NAMESPACE 1 +#endif + +namespace mozilla { + +namespace hal { + +class WindowIdentifier; + +} // namespace hal + +namespace MOZ_HAL_NAMESPACE { + +/** + * Initializes the HAL. This must be called before any other HAL function. + */ +void Init(); + +/** + * Shuts down the HAL. Besides freeing all the used resources this will check + * that all observers have been properly deregistered and assert if not. + */ +void Shutdown(); + +/** + * Turn the default vibrator device on/off per the pattern specified + * by |pattern|. Each element in the pattern is the number of + * milliseconds to turn the vibrator on or off. The first element in + * |pattern| is an "on" element, the next is "off", and so on. + * + * If |pattern| is empty, any in-progress vibration is canceled. + * + * Only an active window within an active tab may call Vibrate; calls + * from inactive windows and windows on inactive tabs do nothing. + * + * If you're calling hal::Vibrate from the outside world, pass an + * nsIDOMWindow* in place of the WindowIdentifier parameter. + * The method with WindowIdentifier will be called automatically. + */ +void Vibrate(const nsTArray<uint32_t>& pattern, nsPIDOMWindowInner* aWindow); +void Vibrate(const nsTArray<uint32_t>& pattern, hal::WindowIdentifier&& id); + +/** + * Cancel a vibration started by the content window identified by + * WindowIdentifier. + * + * If the window was the last window to start a vibration, the + * cancellation request will go through even if the window is not + * active. + * + * As with hal::Vibrate(), if you're calling hal::CancelVibrate from the outside + * world, pass an nsIDOMWindow*. The method with WindowIdentifier will be called + * automatically. + */ +void CancelVibrate(nsPIDOMWindowInner* aWindow); +void CancelVibrate(hal::WindowIdentifier&& id); + +#define MOZ_DEFINE_HAL_OBSERVER(name_) \ + /** \ + * Inform the backend there is a new |name_| observer. \ + * @param aObserver The observer that should be added. \ + */ \ + void Register##name_##Observer(hal::name_##Observer* aObserver); \ + /** \ + * Inform the backend a |name_| observer unregistered. \ + * @param aObserver The observer that should be removed. \ + */ \ + void Unregister##name_##Observer(hal::name_##Observer* aObserver); + +MOZ_DEFINE_HAL_OBSERVER(Battery); + +/** + * Returns the current battery information. + */ +void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo); + +/** + * Notify of a change in the battery state. + * @param aBatteryInfo The new battery information. + */ +void NotifyBatteryChange(const hal::BatteryInformation& aBatteryInfo); + +/** + * Register an observer for the sensor of given type. + * + * The observer will receive data whenever the data generated by the + * sensor is avaiable. + */ +void RegisterSensorObserver(hal::SensorType aSensor, + hal::ISensorObserver* aObserver); + +/** + * Unregister an observer for the sensor of given type. + */ +void UnregisterSensorObserver(hal::SensorType aSensor, + hal::ISensorObserver* aObserver); + +/** + * Post a value generated by a sensor. + * + * This API is internal to hal; clients shouldn't call it directly. + */ +void NotifySensorChange(const hal::SensorData& aSensorData); + +/** + * Enable sensor notifications from the backend + * + * This method is only visible from implementation of sensor manager. + * Rest of the system should not try this. + */ +void EnableSensorNotifications(hal::SensorType aSensor); + +/** + * Disable sensor notifications from the backend + * + * This method is only visible from implementation of sensor manager. + * Rest of the system should not try this. + */ +void DisableSensorNotifications(hal::SensorType aSensor); + +MOZ_DEFINE_HAL_OBSERVER(Network); + +/** + * Returns the current network information. + */ +void GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo); + +/** + * Notify of a change in the network state. + * @param aNetworkInfo The new network information. + */ +void NotifyNetworkChange(const hal::NetworkInformation& aNetworkInfo); + +/** + * Enable wake lock notifications from the backend. + * + * This method is only used by WakeLockObserversManager. + */ +void EnableWakeLockNotifications(); + +/** + * Disable wake lock notifications from the backend. + * + * This method is only used by WakeLockObserversManager. + */ +void DisableWakeLockNotifications(); + +MOZ_DEFINE_HAL_OBSERVER(WakeLock); + +/** + * Adjust a wake lock's counts for the current process. + * + * @param aTopic lock topic + * @param aLockAdjust to increase or decrease active locks + * @param aHiddenAdjust to increase or decrease hidden locks + */ +void ModifyWakeLock(const nsAString& aTopic, hal::WakeLockControl aLockAdjust, + hal::WakeLockControl aHiddenAdjust); + +/** + * Query the wake lock numbers of aTopic. + * @param aTopic lock topic + * @param aWakeLockInfo wake lock numbers + */ +void GetWakeLockInfo(const nsAString& aTopic, + hal::WakeLockInformation* aWakeLockInfo); + +/** + * Notify of a change in the wake lock state. + * @param aWakeLockInfo The new wake lock information. + */ +void NotifyWakeLockChange(const hal::WakeLockInformation& aWakeLockInfo); + +/** + * Lock the screen orientation to the specific orientation. + * @return A promise indicating that the screen orientation has been locked. + */ +[[nodiscard]] RefPtr<GenericNonExclusivePromise> LockScreenOrientation( + const hal::ScreenOrientation& aOrientation); + +/** + * Unlock the screen orientation. + */ +void UnlockScreenOrientation(); + +/** + * Set the priority of the given process. + * + * Exactly what this does will vary between platforms. On *nix we might give + * background processes higher nice values. On other platforms, we might + * ignore this call entirely. + */ +void SetProcessPriority(int aPid, hal::ProcessPriority aPriority); + +/** + * Creates a PerformanceHintSession. + * + * A PerformanceHintSession represents a workload shared by a group of threads + * that should be completed in a target duration each cycle. + * + * Each cycle, the actual work duration should be reported using + * PerformanceHintSession::ReportActualWorkDuration(). The system can then + * adjust the scheduling accordingly in order to achieve the target. + */ +UniquePtr<hal::PerformanceHintSession> CreatePerformanceHintSession( + const nsTArray<PlatformThreadHandle>& aThreads, + mozilla::TimeDuration aTargetWorkDuration); + +/** + * Returns information categorizing the CPUs on the system by performance class. + * + * Returns Nothing if we are unable to calculate the required information. + * + * See the definition of hal::HeterogeneousCpuInfo for more details. + */ +const Maybe<hal::HeterogeneousCpuInfo>& GetHeterogeneousCpuInfo(); + +} // namespace MOZ_HAL_NAMESPACE +} // namespace mozilla + +#ifdef MOZ_DEFINED_HAL_NAMESPACE +# undef MOZ_DEFINED_HAL_NAMESPACE +# undef MOZ_HAL_NAMESPACE +#endif + +#endif // mozilla_Hal_h diff --git a/hal/HalBatteryInformation.h b/hal/HalBatteryInformation.h new file mode 100644 index 0000000000..8e992ef442 --- /dev/null +++ b/hal/HalBatteryInformation.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_HalBatteryInformation_h +#define mozilla_HalBatteryInformation_h + +#include "mozilla/Observer.h" + +namespace mozilla { +namespace hal { +class BatteryInformation; +typedef Observer<hal::BatteryInformation> BatteryObserver; +} // namespace hal +} // namespace mozilla + +#endif // mozilla_HalBatteryInformation_h diff --git a/hal/HalIPCUtils.h b/hal/HalIPCUtils.h new file mode 100644 index 0000000000..5a3f15e573 --- /dev/null +++ b/hal/HalIPCUtils.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_HalIPCUtils_h +#define mozilla_HalIPCUtils_h + +#include "HalScreenConfiguration.h" + +#include "ipc/EnumSerializer.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::hal::ScreenOrientation> + : public BitFlagsEnumSerializer<mozilla::hal::ScreenOrientation, + mozilla::hal::kAllScreenOrientationBits> {}; + +} // namespace IPC + +#endif diff --git a/hal/HalImpl.h b/hal/HalImpl.h new file mode 100644 index 0000000000..eed2492812 --- /dev/null +++ b/hal/HalImpl.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_HalImpl_h +#define mozilla_HalImpl_h + +#ifdef MOZ_UNIFIED_BUILD +# error Cannot use HalImpl.h in unified builds. +#endif + +#define MOZ_HAL_NAMESPACE hal_impl +#undef mozilla_Hal_h +#undef mozilla_HalInternal_h +#include "Hal.h" +#include "HalInternal.h" +#undef MOZ_HAL_NAMESPACE + +#endif // mozilla_HalImpl_h diff --git a/hal/HalInternal.h b/hal/HalInternal.h new file mode 100644 index 0000000000..a26e0a985a --- /dev/null +++ b/hal/HalInternal.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_HalInternal_h +#define mozilla_HalInternal_h 1 + +/* + * This file is included by HalImpl.h and HalSandbox.h with a mechanism similar + * to Hal.h. That means those headers set MOZ_HAL_NAMESPACE to specify in which + * namespace the internal functions should appear. + * + * The difference between Hal.h and HalInternal.h is that methods declared in + * HalInternal.h don't appear in the hal namespace. That also means this file + * should not be included except by HalImpl.h and HalSandbox.h. + */ + +#ifndef MOZ_HAL_NAMESPACE +# error "You shouldn't directly include HalInternal.h!" +#endif + +namespace mozilla { +namespace MOZ_HAL_NAMESPACE { + +/** + * Enables battery notifications from the backend. + */ +void EnableBatteryNotifications(); + +/** + * Disables battery notifications from the backend. + */ +void DisableBatteryNotifications(); + +/** + * Enables network notifications from the backend. + */ +void EnableNetworkNotifications(); + +/** + * Disables network notifications from the backend. + */ +void DisableNetworkNotifications(); + +/** + * Enables screen orientation notifications from the backend. + */ +void EnableScreenConfigurationNotifications(); + +/** + * Disables screen orientation notifications from the backend. + */ +void DisableScreenConfigurationNotifications(); + +/** + * Has the child-side HAL IPC object been destroyed? If so, you shouldn't send + * messages to hal_sandbox. + */ +bool HalChildDestroyed(); +} // namespace MOZ_HAL_NAMESPACE +} // namespace mozilla + +#endif // mozilla_HalInternal_h diff --git a/hal/HalLog.h b/hal/HalLog.h new file mode 100644 index 0000000000..fcf9c5edfa --- /dev/null +++ b/hal/HalLog.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_HalLog_h +#define mozilla_HalLog_h + +#include "mozilla/Logging.h" + +/* + * HalLog.h contains internal macros and functions used for logging. + * + * This should be considered a private include and not used in non-HAL code. + * To enable logging in non-debug builds define the PR_FORCE_LOG macro here. + */ + +namespace mozilla { + +namespace hal { + +mozilla::LogModule* GetHalLog(); +#define HAL_LOG(...) \ + MOZ_LOG(mozilla::hal::GetHalLog(), LogLevel::Debug, (__VA_ARGS__)) +#define HAL_ERR(...) \ + MOZ_LOG(mozilla::hal::GetHalLog(), LogLevel::Error, (__VA_ARGS__)) + +} // namespace hal + +} // namespace mozilla + +#endif // mozilla_HalLog_h diff --git a/hal/HalNetworkInformation.h b/hal/HalNetworkInformation.h new file mode 100644 index 0000000000..9c6b334ddd --- /dev/null +++ b/hal/HalNetworkInformation.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_HalNetworkInformation_h +#define mozilla_HalNetworkInformation_h + +#include "mozilla/Observer.h" + +namespace mozilla { +namespace hal { +class NetworkInformation; +typedef Observer<hal::NetworkInformation> NetworkObserver; +} // namespace hal +} // namespace mozilla + +#endif // mozilla_HalNetworkInformation_h diff --git a/hal/HalSandbox.h b/hal/HalSandbox.h new file mode 100644 index 0000000000..a4be721ea1 --- /dev/null +++ b/hal/HalSandbox.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_hal_HalSandbox_h +#define mozilla_hal_HalSandbox_h + +#define MOZ_HAL_NAMESPACE hal_sandbox +#undef mozilla_Hal_h +#undef mozilla_HalInternal_h +#include "Hal.h" +#include "HalInternal.h" +#undef MOZ_HAL_NAMESPACE + +#endif // mozilla_hal_HalSandbox_h diff --git a/hal/HalScreenConfiguration.h b/hal/HalScreenConfiguration.h new file mode 100644 index 0000000000..9a577884b6 --- /dev/null +++ b/hal/HalScreenConfiguration.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_HalScreenConfiguration_h +#define mozilla_HalScreenConfiguration_h + +#include "mozilla/Observer.h" +#include "mozilla/TypedEnumBits.h" + +// Undo X11/X.h's definition of None +#undef None + +namespace mozilla::hal { + +// Make sure that any change to ScreenOrientation values are also made in +// mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java +enum class ScreenOrientation : uint32_t { + None = 0, + PortraitPrimary = 1u << 0, + PortraitSecondary = 1u << 1, + LandscapePrimary = 1u << 2, + LandscapeSecondary = 1u << 3, + // Default will use the natural orientation for the device, it could be + // PortraitPrimary or LandscapePrimary depends on display resolution + Default = 1u << 4, +}; + +constexpr auto kAllScreenOrientationBits = ScreenOrientation((1 << 5) - 1); + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ScreenOrientation); + +} // namespace mozilla::hal + +#endif // mozilla_HalScreenConfiguration_h diff --git a/hal/HalSensor.h b/hal/HalSensor.h new file mode 100644 index 0000000000..39fc33848b --- /dev/null +++ b/hal/HalSensor.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __HAL_SENSOR_H_ +#define __HAL_SENSOR_H_ + +#include "mozilla/Observer.h" + +namespace mozilla { +namespace hal { + +/** + * Enumeration of sensor types. They are used to specify type while + * register or unregister an observer for a sensor of given type. + * If you add or change any here, do the same in GeckoHalDefines.java. + */ +enum SensorType { + SENSOR_ORIENTATION = 0, + SENSOR_ACCELERATION = 1, + SENSOR_PROXIMITY = 2, + SENSOR_LINEAR_ACCELERATION = 3, + SENSOR_GYROSCOPE = 4, + SENSOR_LIGHT = 5, + SENSOR_ROTATION_VECTOR = 6, + SENSOR_GAME_ROTATION_VECTOR = 7, + NUM_SENSOR_TYPE +}; + +class SensorData; + +typedef Observer<SensorData> ISensorObserver; + +class SensorAccuracy; + +typedef Observer<SensorAccuracy> ISensorAccuracyObserver; + +} // namespace hal +} // namespace mozilla + +#include "ipc/EnumSerializer.h" + +namespace IPC { +/** + * Serializer for SensorType + */ +template <> +struct ParamTraits<mozilla::hal::SensorType> + : public ContiguousEnumSerializer<mozilla::hal::SensorType, + mozilla::hal::SENSOR_ORIENTATION, + mozilla::hal::NUM_SENSOR_TYPE> {}; +} // namespace IPC + +#endif /* __HAL_SENSOR_H_ */ diff --git a/hal/HalTypes.h b/hal/HalTypes.h new file mode 100644 index 0000000000..a04c018307 --- /dev/null +++ b/hal/HalTypes.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_hal_Types_h +#define mozilla_hal_Types_h + +#include "ipc/EnumSerializer.h" +#include "mozilla/BitSet.h" +#include "mozilla/Observer.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace hal { + +/** + * These constants specify special values for content process IDs. You can get + * a content process ID by calling ContentChild::GetID() or + * ContentParent::GetChildID(). + */ +const uint64_t CONTENT_PROCESS_ID_UNKNOWN = uint64_t(-1); +const uint64_t CONTENT_PROCESS_ID_MAIN = 0; + +// Note that we rely on the order of this enum's entries. Higher priorities +// should have larger int values. +enum ProcessPriority { + PROCESS_PRIORITY_UNKNOWN = -1, + PROCESS_PRIORITY_BACKGROUND, + PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE, + PROCESS_PRIORITY_FOREGROUND_KEYBOARD, + // The special class for the preallocated process, high memory priority but + // low CPU priority. + PROCESS_PRIORITY_PREALLOC, + // Any priority greater than or equal to FOREGROUND is considered + // "foreground" for the purposes of priority testing, for example + // CurrentProcessIsForeground(). + PROCESS_PRIORITY_FOREGROUND, + PROCESS_PRIORITY_FOREGROUND_HIGH, + PROCESS_PRIORITY_PARENT_PROCESS, + NUM_PROCESS_PRIORITY +}; + +/** + * Convert a ProcessPriority enum value to a string. The strings returned by + * this function are statically allocated; do not attempt to free one! + * + * If you pass an unknown process priority, we fatally assert in debug + * builds and otherwise return "???". + */ +const char* ProcessPriorityToString(ProcessPriority aPriority); + +/** + * Used by ModifyWakeLock + */ +enum WakeLockControl { + WAKE_LOCK_REMOVE_ONE = -1, + WAKE_LOCK_NO_CHANGE = 0, + WAKE_LOCK_ADD_ONE = 1, + NUM_WAKE_LOCK +}; + +/** + * Represents a workload shared by a group of threads that should be completed + * in a target duration each cycle. + * + * This is created using hal::CreatePerformanceHintSession(). Each cycle, the + * actual work duration should be reported using ReportActualWorkDuration(). The + * system can then adjust the scheduling accordingly in order to achieve the + * target. + */ +class PerformanceHintSession { + public: + virtual ~PerformanceHintSession() = default; + + // Updates the session's target work duration for each cycle. + virtual void UpdateTargetWorkDuration(TimeDuration aDuration) = 0; + + // Reports the session's actual work duration for a cycle. + virtual void ReportActualWorkDuration(TimeDuration aDuration) = 0; +}; + +/** + * Categorizes the CPUs on the system in to big, medium, and little classes. + * + * A set bit in each bitset indicates that the CPU of that index belongs to that + * class. If the CPUs are fully homogeneous they are all categorized as big. If + * there are only 2 classes, they are categorized as either big or little. + * Finally, if there are >= 3 classes, the remainder will be categorized as + * medium. + * + * If there are more than MAX_CPUS present we are unable to represent this + * information. + */ +struct HeterogeneousCpuInfo { + // We use a max of 32 because this was initially implemented only for Android + // where we are unlikely to need more CPUs than that, and it simplifies + // dealing with cpu_set_t as CPU_SETSIZE is 32 on 32-bit Android. + // If there are more than 32 CPU cores, the implementation should try to fill + // first mBigCpus before adding anything to mMediumCpus or mLittleCpus. + static const size_t MAX_CPUS = 32; + size_t mTotalNumCpus; + mozilla::BitSet<MAX_CPUS> mLittleCpus; + mozilla::BitSet<MAX_CPUS> mMediumCpus; + mozilla::BitSet<MAX_CPUS> mBigCpus; +}; + +} // namespace hal +} // namespace mozilla + +namespace IPC { + +/** + * WakeLockControl serializer. + */ +template <> +struct ParamTraits<mozilla::hal::WakeLockControl> + : public ContiguousEnumSerializer<mozilla::hal::WakeLockControl, + mozilla::hal::WAKE_LOCK_REMOVE_ONE, + mozilla::hal::NUM_WAKE_LOCK> {}; + +template <> +struct ParamTraits<mozilla::hal::ProcessPriority> + : public ContiguousEnumSerializer<mozilla::hal::ProcessPriority, + mozilla::hal::PROCESS_PRIORITY_UNKNOWN, + mozilla::hal::NUM_PROCESS_PRIORITY> {}; + +} // namespace IPC + +#endif // mozilla_hal_Types_h diff --git a/hal/HalWakeLock.cpp b/hal/HalWakeLock.cpp new file mode 100644 index 0000000000..80ef930567 --- /dev/null +++ b/hal/HalWakeLock.cpp @@ -0,0 +1,273 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "base/process_util.h" +#include "mozilla/FOGIPC.h" +#include "mozilla/HalWakeLock.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "nsClassHashtable.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsIPropertyBag2.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" + +using namespace mozilla; +using namespace mozilla::hal; + +namespace { + +struct LockCount { + LockCount() : numLocks(0), numHidden(0) {} + uint32_t numLocks; + uint32_t numHidden; + CopyableTArray<uint64_t> processes; +}; + +typedef nsTHashMap<nsUint64HashKey, LockCount> ProcessLockTable; +typedef nsClassHashtable<nsStringHashKey, ProcessLockTable> LockTable; + +int sActiveListeners = 0; +StaticAutoPtr<LockTable> sLockTable; +bool sIsShuttingDown = false; + +WakeLockInformation WakeLockInfoFromLockCount(const nsAString& aTopic, + const LockCount& aLockCount) { + nsString topic(aTopic); + WakeLockInformation info(topic, aLockCount.numLocks, aLockCount.numHidden, + aLockCount.processes); + + return info; +} + +static void CountWakeLocks(ProcessLockTable* aTable, LockCount* aTotalCount) { + for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) { + const uint64_t& key = iter.Key(); + LockCount count = iter.UserData(); + + aTotalCount->numLocks += count.numLocks; + aTotalCount->numHidden += count.numHidden; + + // This is linear in the number of processes, but that should be small. + if (!aTotalCount->processes.Contains(key)) { + aTotalCount->processes.AppendElement(key); + } + } +} + +class ClearHashtableOnShutdown final : public nsIObserver { + ~ClearHashtableOnShutdown() {} + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +}; + +NS_IMPL_ISUPPORTS(ClearHashtableOnShutdown, nsIObserver) + +NS_IMETHODIMP +ClearHashtableOnShutdown::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* data) { + MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown")); + + sIsShuttingDown = true; + sLockTable = nullptr; + + return NS_OK; +} + +class CleanupOnContentShutdown final : public nsIObserver { + ~CleanupOnContentShutdown() {} + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +}; + +NS_IMPL_ISUPPORTS(CleanupOnContentShutdown, nsIObserver) + +NS_IMETHODIMP +CleanupOnContentShutdown::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* data) { + MOZ_ASSERT(!strcmp(aTopic, "ipc:content-shutdown")); + + if (sIsShuttingDown) { + return NS_OK; + } + + nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject); + if (!props) { + NS_WARNING("ipc:content-shutdown message without property bag as subject"); + return NS_OK; + } + + uint64_t childID = 0; + nsresult rv = props->GetPropertyAsUint64(u"childID"_ns, &childID); + if (NS_SUCCEEDED(rv)) { + for (auto iter = sLockTable->Iter(); !iter.Done(); iter.Next()) { + auto table = iter.UserData(); + + if (table->Get(childID, nullptr)) { + table->Remove(childID); + + LockCount totalCount; + CountWakeLocks(table, &totalCount); + + if (sActiveListeners) { + NotifyWakeLockChange( + WakeLockInfoFromLockCount(iter.Key(), totalCount)); + } + + if (totalCount.numLocks == 0) { + iter.Remove(); + } + } + } + } else { + NS_WARNING("ipc:content-shutdown message without childID property"); + } + return NS_OK; +} + +} // namespace + +namespace mozilla { + +namespace hal { + +void WakeLockInit() { + sLockTable = new LockTable(); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(new ClearHashtableOnShutdown(), "xpcom-shutdown", false); + obs->AddObserver(new CleanupOnContentShutdown(), "ipc:content-shutdown", + false); + } +} + +WakeLockState ComputeWakeLockState(int aNumLocks, int aNumHidden) { + if (aNumLocks == 0) { + return WAKE_LOCK_STATE_UNLOCKED; + } else if (aNumLocks == aNumHidden) { + return WAKE_LOCK_STATE_HIDDEN; + } else { + return WAKE_LOCK_STATE_VISIBLE; + } +} + +} // namespace hal + +namespace hal_impl { + +void EnableWakeLockNotifications() { sActiveListeners++; } + +void DisableWakeLockNotifications() { sActiveListeners--; } + +void ModifyWakeLockWithChildID(const nsAString& aTopic, + hal::WakeLockControl aLockAdjust, + hal::WakeLockControl aHiddenAdjust, + uint64_t aChildID) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aChildID != CONTENT_PROCESS_ID_UNKNOWN); + + if (sIsShuttingDown) { + return; + } + + LockCount processCount; + LockCount totalCount; + ProcessLockTable* const table = + sLockTable->WithEntryHandle(aTopic, [&](auto&& entry) { + if (!entry) { + entry.Insert(MakeUnique<ProcessLockTable>()); + } else { + Unused << entry.Data()->Get(aChildID, &processCount); + CountWakeLocks(entry->get(), &totalCount); + } + return entry->get(); + }); + + MOZ_ASSERT(processCount.numLocks >= processCount.numHidden); + MOZ_ASSERT(aLockAdjust >= 0 || processCount.numLocks > 0); + MOZ_ASSERT(aHiddenAdjust >= 0 || processCount.numHidden > 0); + MOZ_ASSERT(totalCount.numLocks >= totalCount.numHidden); + MOZ_ASSERT(aLockAdjust >= 0 || totalCount.numLocks > 0); + MOZ_ASSERT(aHiddenAdjust >= 0 || totalCount.numHidden > 0); + + WakeLockState oldState = + ComputeWakeLockState(totalCount.numLocks, totalCount.numHidden); + + if (ComputeWakeLockState(totalCount.numLocks + aLockAdjust, + totalCount.numHidden + aHiddenAdjust) != oldState && + (aTopic.Equals(u"video-playing"_ns) || + aTopic.Equals(u"audio-playing"_ns))) { + glean::RecordPowerMetrics(); + } + + bool processWasLocked = processCount.numLocks > 0; + + processCount.numLocks += aLockAdjust; + processCount.numHidden += aHiddenAdjust; + + totalCount.numLocks += aLockAdjust; + totalCount.numHidden += aHiddenAdjust; + + if (processCount.numLocks) { + table->InsertOrUpdate(aChildID, processCount); + } else { + table->Remove(aChildID); + } + if (!totalCount.numLocks) { + sLockTable->Remove(aTopic); + } + + if (sActiveListeners && + (oldState != + ComputeWakeLockState(totalCount.numLocks, totalCount.numHidden) || + processWasLocked != (processCount.numLocks > 0))) { + WakeLockInformation info; + hal::GetWakeLockInfo(aTopic, &info); + NotifyWakeLockChange(info); + } +} + +void ModifyWakeLock(const nsAString& aTopic, hal::WakeLockControl aLockAdjust, + hal::WakeLockControl aHiddenAdjust) { + ModifyWakeLockWithChildID(aTopic, aLockAdjust, aHiddenAdjust, + CONTENT_PROCESS_ID_MAIN); +} + +void GetWakeLockInfo(const nsAString& aTopic, + WakeLockInformation* aWakeLockInfo) { + if (sIsShuttingDown) { + NS_WARNING( + "You don't want to get wake lock information during xpcom-shutdown!"); + *aWakeLockInfo = WakeLockInformation(); + return; + } + + if (!sLockTable) { + // This can happen during some gtests. + NS_WARNING("Attempting to get wake lock information before initialization"); + *aWakeLockInfo = WakeLockInformation(); + return; + } + + ProcessLockTable* table = sLockTable->Get(aTopic); + if (!table) { + *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, LockCount()); + return; + } + LockCount totalCount; + CountWakeLocks(table, &totalCount); + *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, totalCount); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/HalWakeLock.h b/hal/HalWakeLock.h new file mode 100644 index 0000000000..880da4214d --- /dev/null +++ b/hal/HalWakeLock.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __HAL_WAKELOCK_H_ +#define __HAL_WAKELOCK_H_ + +namespace mozilla { +namespace hal { + +enum WakeLockState { + WAKE_LOCK_STATE_UNLOCKED, + WAKE_LOCK_STATE_HIDDEN, + WAKE_LOCK_STATE_VISIBLE +}; + +/** + * Return the wake lock state according to the numbers. + */ +WakeLockState ComputeWakeLockState(int aNumLocks, int aNumHidden); + +} // namespace hal + +namespace hal_impl { +void ModifyWakeLockWithChildID(const nsAString& aTopic, + hal::WakeLockControl aLockAdjust, + hal::WakeLockControl aHiddenAdjust, + uint64_t aChildID); +} // namespace hal_impl + +} // namespace mozilla + +#endif /* __HAL_WAKELOCK_H_ */ diff --git a/hal/HalWakeLockInformation.h b/hal/HalWakeLockInformation.h new file mode 100644 index 0000000000..2f52b1810e --- /dev/null +++ b/hal/HalWakeLockInformation.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_HalWakeLockInformation_h +#define mozilla_HalWakeLockInformation_h + +#include "mozilla/Observer.h" + +namespace mozilla { +namespace hal { +class WakeLockInformation; +typedef Observer<hal::WakeLockInformation> WakeLockObserver; +} // namespace hal +} // namespace mozilla + +#endif // mozilla_HalWakeLockInformation_h diff --git a/hal/HalWakeLockInternal.h b/hal/HalWakeLockInternal.h new file mode 100644 index 0000000000..3f99f2a1e1 --- /dev/null +++ b/hal/HalWakeLockInternal.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __HAL_WAKELOCK_INTERNAL_H_ +#define __HAL_WAKELOCK_INTERNAL_H_ + +namespace mozilla { +namespace hal { + +void WakeLockInit(); + +} // namespace hal +} // namespace mozilla + +#endif /* __HAL_WAKELOCK_INTERNAL_H_ */ diff --git a/hal/WindowIdentifier.cpp b/hal/WindowIdentifier.cpp new file mode 100644 index 0000000000..dc10801691 --- /dev/null +++ b/hal/WindowIdentifier.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WindowIdentifier.h" + +#include "mozilla/dom/ContentChild.h" +#include "nsPIDOMWindow.h" + +namespace mozilla { +namespace hal { + +WindowIdentifier::WindowIdentifier() + : mWindow(nullptr) +#ifdef DEBUG + , + mIsEmpty(true) +#endif +{ +} + +WindowIdentifier::WindowIdentifier(nsPIDOMWindowInner* window) + : mWindow(window) { + mID.AppendElement(GetWindowID()); +} + +WindowIdentifier::WindowIdentifier(nsTArray<uint64_t>&& id, + nsPIDOMWindowInner* window) + : mID(std::move(id)), mWindow(window) { + mID.AppendElement(GetWindowID()); +} + +const nsTArray<uint64_t>& WindowIdentifier::AsArray() const { + MOZ_ASSERT(!mIsEmpty); + return mID; +} + +bool WindowIdentifier::HasTraveledThroughIPC() const { + MOZ_ASSERT(!mIsEmpty); + return mID.Length() >= 2; +} + +void WindowIdentifier::AppendProcessID() { + MOZ_ASSERT(!mIsEmpty); + mID.AppendElement(dom::ContentChild::GetSingleton()->GetID()); +} + +uint64_t WindowIdentifier::GetWindowID() const { + MOZ_ASSERT(!mIsEmpty); + return mWindow ? mWindow->WindowID() : UINT64_MAX; +} + +nsPIDOMWindowInner* WindowIdentifier::GetWindow() const { + MOZ_ASSERT(!mIsEmpty); + return mWindow; +} + +} // namespace hal +} // namespace mozilla diff --git a/hal/WindowIdentifier.h b/hal/WindowIdentifier.h new file mode 100644 index 0000000000..11c5140f61 --- /dev/null +++ b/hal/WindowIdentifier.h @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_hal_WindowIdentifier_h +#define mozilla_hal_WindowIdentifier_h + +#include "mozilla/Types.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class nsPIDOMWindowInner; + +namespace mozilla { +namespace hal { + +/** + * This class serves two purposes. + * + * First, this class wraps a pointer to a window. + * + * Second, WindowIdentifier lets us uniquely identify a window across + * processes. A window exposes an ID which is unique only within its + * process. Thus to identify a window, we need to know the ID of the + * process which contains it. But the scope of a process's ID is its + * parent; that is, two processes with different parents might have + * the same ID. + * + * So to identify a window, we need its ID plus the IDs of all the + * processes in the path from the window's process to the root + * process. We throw in the IDs of the intermediate windows (a + * content window is contained in a window at each level of the + * process tree) for good measures. + * + * You can access this list of IDs by calling AsArray(). + */ +class WindowIdentifier { + public: + /** + * Create an empty WindowIdentifier. Calls to any of this object's + * public methods will assert -- an empty WindowIdentifier may be + * used only as a placeholder to code which promises not to touch + * the object. + */ + WindowIdentifier(); + + /** + * Wrap the given window in a WindowIdentifier. These two + * constructors automatically grab the window's ID and append it to + * the array of IDs. + * + * Note that these constructors allow an implicit conversion to a + * WindowIdentifier. + */ + explicit WindowIdentifier(nsPIDOMWindowInner* window); + + /** + * Create a new WindowIdentifier with the given id array and window. + * This automatically grabs the window's ID and appends it to the + * array. + */ + WindowIdentifier(nsTArray<uint64_t>&& id, nsPIDOMWindowInner* window); + + /** + * Get the list of window and process IDs we contain. + */ + typedef nsTArray<uint64_t> IDArrayType; + const IDArrayType& AsArray() const; + + /** + * Append the ID of the ContentChild singleton to our array of + * window/process IDs. + */ + void AppendProcessID(); + + /** + * Does this WindowIdentifier identify both a window and the process + * containing that window? If so, we say it has traveled through + * IPC. + */ + bool HasTraveledThroughIPC() const; + + /** + * Get the window this object wraps. + */ + nsPIDOMWindowInner* GetWindow() const; + + private: + /** + * Get the ID of the window object we wrap. + */ + uint64_t GetWindowID() const; + + AutoTArray<uint64_t, 3> mID; + nsCOMPtr<nsPIDOMWindowInner> mWindow; +#ifdef DEBUG + bool mIsEmpty = false; +#endif +}; + +} // namespace hal +} // namespace mozilla + +#endif // mozilla_hal_WindowIdentifier_h diff --git a/hal/android/AndroidHal.cpp b/hal/android/AndroidHal.cpp new file mode 100644 index 0000000000..3a7e1bf121 --- /dev/null +++ b/hal/android/AndroidHal.cpp @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalImpl.h" +#include "WindowIdentifier.h" +#include "AndroidBridge.h" +#include "mozilla/dom/network/Constants.h" +#include "mozilla/java/GeckoAppShellWrappers.h" +#include "mozilla/java/GeckoRuntimeWrappers.h" +#include "mozilla/widget/ScreenManager.h" +#include "nsPIDOMWindow.h" + +using namespace mozilla::dom; +using namespace mozilla::hal; + +namespace mozilla::hal_impl { + +void Vibrate(const nsTArray<uint32_t>& pattern, WindowIdentifier&&) { + // Ignore the WindowIdentifier parameter; it's here only because hal::Vibrate, + // hal_sandbox::Vibrate, and hal_impl::Vibrate all must have the same + // signature. + + // Strangely enough, the Android Java API seems to treat vibrate([0]) as a + // nop. But we want to treat vibrate([0]) like CancelVibrate! (Note that we + // also need to treat vibrate([]) as a call to CancelVibrate.) + bool allZero = true; + for (uint32_t i = 0; i < pattern.Length(); i++) { + if (pattern[i] != 0) { + allZero = false; + break; + } + } + + if (allZero) { + hal_impl::CancelVibrate(WindowIdentifier()); + return; + } + + AndroidBridge* b = AndroidBridge::Bridge(); + if (!b) { + return; + } + + b->Vibrate(pattern); +} + +void CancelVibrate(WindowIdentifier&&) { + // Ignore WindowIdentifier parameter. + + java::GeckoAppShell::CancelVibrate(); +} + +void EnableBatteryNotifications() { + java::GeckoAppShell::EnableBatteryNotifications(); +} + +void DisableBatteryNotifications() { + java::GeckoAppShell::DisableBatteryNotifications(); +} + +void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) { + AndroidBridge::Bridge()->GetCurrentBatteryInformation(aBatteryInfo); +} + +void EnableNetworkNotifications() { + java::GeckoAppShell::EnableNetworkNotifications(); +} + +void DisableNetworkNotifications() { + java::GeckoAppShell::DisableNetworkNotifications(); +} + +void GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo) { + AndroidBridge::Bridge()->GetCurrentNetworkInformation(aNetworkInfo); +} + +static bool IsSupportedScreenOrientation(hal::ScreenOrientation aOrientation) { + // The Android backend only supports these orientations. + static constexpr ScreenOrientation kSupportedOrientations[] = { + ScreenOrientation::PortraitPrimary, + ScreenOrientation::PortraitSecondary, + ScreenOrientation::PortraitPrimary | ScreenOrientation::PortraitSecondary, + ScreenOrientation::LandscapePrimary, + ScreenOrientation::LandscapeSecondary, + ScreenOrientation::LandscapePrimary | + ScreenOrientation::LandscapeSecondary, + ScreenOrientation::PortraitPrimary | + ScreenOrientation::PortraitSecondary | + ScreenOrientation::LandscapePrimary | + ScreenOrientation::LandscapeSecondary, + ScreenOrientation::Default, + }; + for (auto supportedOrientation : kSupportedOrientations) { + if (aOrientation == supportedOrientation) { + return true; + } + } + return false; +} + +RefPtr<GenericNonExclusivePromise> LockScreenOrientation( + const hal::ScreenOrientation& aOrientation) { + if (!IsSupportedScreenOrientation(aOrientation)) { + NS_WARNING("Unsupported screen orientation type"); + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_DOM_NOT_SUPPORTED_ERR, __func__); + } + + java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance(); + if (!runtime) { + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR, + __func__); + } + + hal::ScreenOrientation orientation = [&aOrientation]() { + if (aOrientation == hal::ScreenOrientation::Default) { + // GeckoView only supports single monitor, so get primary screen data for + // natural orientation. + RefPtr<widget::Screen> screen = + widget::ScreenManager::GetSingleton().GetPrimaryScreen(); + return screen->GetDefaultOrientationType(); + } + return aOrientation; + }(); + + auto result = runtime->LockScreenOrientation(uint32_t(orientation)); + auto geckoResult = java::GeckoResult::LocalRef(std::move(result)); + return GenericNonExclusivePromise::FromGeckoResult(geckoResult) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [](const GenericNonExclusivePromise::ResolveOrRejectValue& aValue) { + if (aValue.IsResolve()) { + if (aValue.ResolveValue()) { + return GenericNonExclusivePromise::CreateAndResolve(true, + __func__); + } + // Delegated orientation controller returns failure for + // lock. + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_DOM_ABORT_ERR, __func__); + } + // Browser side doesn't implement orientation delegate. + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_DOM_NOT_SUPPORTED_ERR, __func__); + }); +} + +void UnlockScreenOrientation() { + java::GeckoRuntime::LocalRef runtime = java::GeckoRuntime::GetInstance(); + if (runtime) { + runtime->UnlockScreenOrientation(); + } +} + +} // namespace mozilla::hal_impl diff --git a/hal/android/AndroidHeterogeneousCpuInfo.cpp b/hal/android/AndroidHeterogeneousCpuInfo.cpp new file mode 100644 index 0000000000..e51ebce260 --- /dev/null +++ b/hal/android/AndroidHeterogeneousCpuInfo.cpp @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalLog.h" +#include "HalTypes.h" + +#include "mozilla/BitSet.h" +#include "mozilla/CheckedInt.h" +#include "prsystem.h" +#include <fstream> + +namespace mozilla::hal_impl { + +mozilla::Maybe<HeterogeneousCpuInfo> CreateHeterogeneousCpuInfo() { + CheckedInt<size_t> cpuCount = PR_GetNumberOfProcessors(); + if (!cpuCount.isValid()) { + HAL_ERR("HeterogeneousCpuInfo: Failed to query processor count"); + return Nothing(); + } + + if (cpuCount.value() > HeterogeneousCpuInfo::MAX_CPUS) { + HAL_ERR("HeterogeneousCpuInfo: Too many cpus"); + return Nothing(); + } + + std::vector<std::pair<int, int>> cpu_freqs; + cpu_freqs.reserve(cpuCount.value()); + for (size_t i = 0; i < cpuCount.value(); i++) { + std::stringstream freq_filename; + // On all devices tested, even with isolated content processes we do have + // permission to read this file. If, however, this function stops working + // one day, then this may be a good place to start investigating. + freq_filename << "/sys/devices/system/cpu/cpu" << i + << "/cpufreq/cpuinfo_max_freq"; + std::ifstream ifs(freq_filename.str()); + if (!ifs) { + HAL_ERR("HeterogeneousCpuInfo: Failed to open file %s", + freq_filename.str().c_str()); + return Nothing(); + } + int freq; + if (!(ifs >> freq)) { + HAL_ERR("HeterogeneousCpuInfo: Failed to parse file %s", + freq_filename.str().c_str()); + return Nothing(); + } + cpu_freqs.push_back(std::make_pair(i, freq)); + } + + std::sort(cpu_freqs.begin(), cpu_freqs.end(), + [](auto lhs, auto rhs) { return lhs.second < rhs.second; }); + + HeterogeneousCpuInfo info; + info.mTotalNumCpus = cpuCount.value(); + + int lowest_freq = cpu_freqs.front().second; + int highest_freq = cpu_freqs.back().second; + for (const auto& [cpu, freq] : cpu_freqs) { + if (freq == highest_freq) { + info.mBigCpus[cpu] = true; + } else if (freq == lowest_freq) { + info.mLittleCpus[cpu] = true; + } else { + info.mMediumCpus[cpu] = true; + } + } + + HAL_LOG("HeterogeneousCpuInfo: little: %zu, med: %zu, big: %zu", + info.mLittleCpus.Count(), info.mMediumCpus.Count(), + info.mBigCpus.Count()); + + return Some(info); +} + +const Maybe<HeterogeneousCpuInfo>& GetHeterogeneousCpuInfo() { + static const Maybe<HeterogeneousCpuInfo> cpuInfo = + CreateHeterogeneousCpuInfo(); + return cpuInfo; +} + +} // namespace mozilla::hal_impl diff --git a/hal/android/AndroidPerformanceHintManager.cpp b/hal/android/AndroidPerformanceHintManager.cpp new file mode 100644 index 0000000000..a9507a8272 --- /dev/null +++ b/hal/android/AndroidPerformanceHintManager.cpp @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalLog.h" +#include "HalTypes.h" + +#include "AndroidBuild.h" + +#include <dlfcn.h> +#include <stddef.h> +#include <stdint.h> +#include <sys/types.h> + +typedef struct APerformanceHintManager APerformanceHintManager; +typedef struct APerformanceHintSession APerformanceHintSession; + +namespace mozilla { +namespace hal_impl { + +#define LOAD_FN(api, lib, name) \ + do { \ + api->m##name = reinterpret_cast<Fn##name>(dlsym(handle, #name)); \ + if (!api->m##name) { \ + HAL_ERR("Failed to load %s", #name); \ + return nullptr; \ + } \ + } while (false) + +class PerformanceHintManagerApi final { + public: + static PerformanceHintManagerApi* Get() { + // C++ guarantees local static variable initialization is thread safe + static UniquePtr<PerformanceHintManagerApi> api = Create(); + return api.get(); + } + + APerformanceHintManager* APerformanceHint_getManager() const { + return mAPerformanceHint_getManager(); + } + + APerformanceHintSession* APerformanceHint_createSession( + APerformanceHintManager* manager, const int32_t* threadIds, size_t size, + int64_t initialTargetWorkDurationNanos) const { + return mAPerformanceHint_createSession(manager, threadIds, size, + initialTargetWorkDurationNanos); + } + + int APerformanceHint_updateTargetWorkDuration( + APerformanceHintSession* session, int64_t targetDurationNanos) const { + return mAPerformanceHint_updateTargetWorkDuration(session, + targetDurationNanos); + } + + int APerformanceHint_reportActualWorkDuration( + APerformanceHintSession* session, int64_t actualDurationNanos) const { + return mAPerformanceHint_reportActualWorkDuration(session, + actualDurationNanos); + } + + void APerformanceHint_closeSession(APerformanceHintSession* session) const { + mAPerformanceHint_closeSession(session); + } + + private: + PerformanceHintManagerApi() = default; + + static UniquePtr<PerformanceHintManagerApi> Create() { + if (mozilla::jni::GetAPIVersion() < __ANDROID_API_T__) { + return nullptr; + } + + void* const handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL); + if (!handle) { + HAL_ERR("Failed to open libandroid.so"); + return nullptr; + } + + auto api = WrapUnique(new PerformanceHintManagerApi()); + LOAD_FN(api, handle, APerformanceHint_getManager); + LOAD_FN(api, handle, APerformanceHint_createSession); + LOAD_FN(api, handle, APerformanceHint_updateTargetWorkDuration); + LOAD_FN(api, handle, APerformanceHint_reportActualWorkDuration); + LOAD_FN(api, handle, APerformanceHint_closeSession); + + return api; + } + + using FnAPerformanceHint_getManager = APerformanceHintManager* (*)(); + using FnAPerformanceHint_createSession = + APerformanceHintSession* (*)(APerformanceHintManager* manager, + const int32_t* threadIds, size_t size, + int64_t initialTargetWorkDurationNanos); + using FnAPerformanceHint_updateTargetWorkDuration = + int (*)(APerformanceHintSession* session, int64_t targetDurationNanos); + using FnAPerformanceHint_reportActualWorkDuration = + int (*)(APerformanceHintSession* session, int64_t actualDurationNanos); + using FnAPerformanceHint_closeSession = + void (*)(APerformanceHintSession* session); + + FnAPerformanceHint_getManager mAPerformanceHint_getManager = nullptr; + FnAPerformanceHint_createSession mAPerformanceHint_createSession = nullptr; + FnAPerformanceHint_updateTargetWorkDuration + mAPerformanceHint_updateTargetWorkDuration = nullptr; + FnAPerformanceHint_reportActualWorkDuration + mAPerformanceHint_reportActualWorkDuration = nullptr; + FnAPerformanceHint_closeSession mAPerformanceHint_closeSession = nullptr; +}; + +class AndroidPerformanceHintSession final : public hal::PerformanceHintSession { + public: + // Creates a PerformanceHintSession wrapping the provided NDK + // APerformanceHintSession instance. This assumes ownership of aSession, + // therefore the caller must not close the session itself. + explicit AndroidPerformanceHintSession(APerformanceHintSession* aSession) + : mSession(aSession) {} + + AndroidPerformanceHintSession(AndroidPerformanceHintSession& aOther) = delete; + AndroidPerformanceHintSession(AndroidPerformanceHintSession&& aOther) { + mSession = aOther.mSession; + aOther.mSession = nullptr; + } + + ~AndroidPerformanceHintSession() { + if (mSession) { + PerformanceHintManagerApi::Get()->APerformanceHint_closeSession(mSession); + } + } + + void UpdateTargetWorkDuration(TimeDuration aDuration) override { + PerformanceHintManagerApi::Get()->APerformanceHint_updateTargetWorkDuration( + mSession, aDuration.ToMicroseconds() * 1000); + } + + void ReportActualWorkDuration(TimeDuration aDuration) override { + PerformanceHintManagerApi::Get()->APerformanceHint_reportActualWorkDuration( + mSession, aDuration.ToMicroseconds() * 1000); + } + + private: + APerformanceHintSession* mSession; +}; + +static APerformanceHintManager* InitManager() { + const auto* api = PerformanceHintManagerApi::Get(); + if (!api) { + return nullptr; + } + + // At the time of writing we are only aware of PerformanceHintManager being + // implemented on Tensor devices (Pixel 6 and 7 families). On most devices + // createSession() will simply return null. However, on some devices + // createSession() does return a session but scheduling does not appear to be + // affected in any way. Rather than pretending to the caller that + // PerformanceHintManager is available on such devices, return null allowing + // them to use another means of achieving the performance they require. + const auto socManufacturer = java::sdk::Build::SOC_MANUFACTURER()->ToString(); + if (!socManufacturer.EqualsASCII("Google")) { + return nullptr; + } + + return api->APerformanceHint_getManager(); +} + +UniquePtr<hal::PerformanceHintSession> CreatePerformanceHintSession( + const nsTArray<PlatformThreadHandle>& aThreads, + mozilla::TimeDuration aTargetWorkDuration) { + // C++ guarantees local static variable initialization is thread safe + static APerformanceHintManager* manager = InitManager(); + + if (!manager) { + return nullptr; + } + + const auto* api = PerformanceHintManagerApi::Get(); + + nsTArray<pid_t> tids(aThreads.Length()); + std::transform(aThreads.cbegin(), aThreads.cend(), MakeBackInserter(tids), + [](pthread_t handle) { return pthread_gettid_np(handle); }); + + APerformanceHintSession* session = api->APerformanceHint_createSession( + manager, tids.Elements(), tids.Length(), + aTargetWorkDuration.ToMicroseconds() * 1000); + if (!session) { + return nullptr; + } + + return MakeUnique<AndroidPerformanceHintSession>(session); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/android/AndroidProcessPriority.cpp b/hal/android/AndroidProcessPriority.cpp new file mode 100644 index 0000000000..aab2bf50ef --- /dev/null +++ b/hal/android/AndroidProcessPriority.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" + +#include "mozilla/java/GeckoProcessManagerWrappers.h" +#include "mozilla/java/GeckoProcessTypeWrappers.h" +#include "mozilla/java/ServiceAllocatorWrappers.h" + +using namespace mozilla::hal; + +/** + * Bucket the Gecko HAL process priority level into one of the three Android + * priority levels. + */ +static mozilla::java::ServiceAllocator::PriorityLevel::LocalRef +ToJavaPriorityLevel(const ProcessPriority aPriority) { + if (aPriority >= PROCESS_PRIORITY_FOREGROUND) { + return mozilla::java::ServiceAllocator::PriorityLevel::FOREGROUND(); + } else if (aPriority <= PROCESS_PRIORITY_PREALLOC && + aPriority >= PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE) { + return mozilla::java::ServiceAllocator::PriorityLevel::BACKGROUND(); + } + + return mozilla::java::ServiceAllocator::PriorityLevel::IDLE(); +} + +namespace mozilla { +namespace hal_impl { + +void SetProcessPriority(int aPid, ProcessPriority aPriority) { + if (aPriority == PROCESS_PRIORITY_PARENT_PROCESS) { + // This is the parent process itself, which we do not control. + return; + } + + const int32_t intPriority = static_cast<int32_t>(aPriority); + if (intPriority < 0 || intPriority >= NUM_PROCESS_PRIORITY) { + return; + } + + auto contentProcType = java::GeckoProcessType::CONTENT(); + auto selector = + java::GeckoProcessManager::Selector::New(contentProcType, aPid); + auto priorityLevel = ToJavaPriorityLevel(aPriority); + + // To Android, a lower-valued integer is a higher relative priority. + // We take the integer value of the enum and subtract it from the value + // of the highest Gecko priority level to obtain a 0-based indicator of + // the relative priority within the Java PriorityLevel. + const int32_t relativeImportance = + (static_cast<int32_t>(NUM_PROCESS_PRIORITY) - 1) - intPriority; + + java::GeckoProcessManager::SetProcessPriority(selector, priorityLevel, + relativeImportance); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/android/AndroidSensor.cpp b/hal/android/AndroidSensor.cpp new file mode 100644 index 0000000000..59db64a1d3 --- /dev/null +++ b/hal/android/AndroidSensor.cpp @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "mozilla/java/GeckoAppShellWrappers.h" + +using namespace mozilla::hal; + +namespace mozilla { +namespace hal_impl { + +void EnableSensorNotifications(SensorType aSensor) { + java::GeckoAppShell::EnableSensor(aSensor); +} + +void DisableSensorNotifications(SensorType aSensor) { + java::GeckoAppShell::DisableSensor(aSensor); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/cocoa/CocoaBattery.cpp b/hal/cocoa/CocoaBattery.cpp new file mode 100644 index 0000000000..a6c4b689e1 --- /dev/null +++ b/hal/cocoa/CocoaBattery.cpp @@ -0,0 +1,317 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim set: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#import <CoreFoundation/CoreFoundation.h> +#import <IOKit/ps/IOPowerSources.h> +#import <IOKit/ps/IOPSKeys.h> + +#include <mozilla/Hal.h> +#include <mozilla/dom/battery/Constants.h> +#include <mozilla/Services.h> + +#include <nsIObserverService.h> +#include <nsIObserver.h> + +#include <dlfcn.h> + +#define IOKIT_FRAMEWORK_PATH "/System/Library/Frameworks/IOKit.framework/IOKit" + +#ifndef kIOPSTimeRemainingUnknown +# define kIOPSTimeRemainingUnknown ((CFTimeInterval)-1.0) +#endif +#ifndef kIOPSTimeRemainingUnlimited +# define kIOPSTimeRemainingUnlimited ((CFTimeInterval)-2.0) +#endif + +using namespace mozilla::dom::battery; + +namespace mozilla { +namespace hal_impl { + +typedef CFTimeInterval (*IOPSGetTimeRemainingEstimateFunc)(void); + +class MacPowerInformationService { + public: + static MacPowerInformationService* GetInstance(); + static void Shutdown(); + static bool IsShuttingDown(); + + void BeginListening(); + void StopListening(); + + static void HandleChange(void* aContext); + + ~MacPowerInformationService(); + + private: + MacPowerInformationService(); + + // The reference to the runloop that is notified of power changes. + CFRunLoopSourceRef mRunLoopSource; + + double mLevel; + bool mCharging; + double mRemainingTime; + bool mShouldNotify; + + friend void GetCurrentBatteryInformation( + hal::BatteryInformation* aBatteryInfo); + + static MacPowerInformationService* sInstance; + static bool sShuttingDown; + + static void* sIOKitFramework; + static IOPSGetTimeRemainingEstimateFunc sIOPSGetTimeRemainingEstimate; +}; + +void* MacPowerInformationService::sIOKitFramework; +IOPSGetTimeRemainingEstimateFunc + MacPowerInformationService::sIOPSGetTimeRemainingEstimate; + +/* + * Implementation of mozilla::hal_impl::EnableBatteryNotifications, + * mozilla::hal_impl::DisableBatteryNotifications, + * and mozilla::hal_impl::GetCurrentBatteryInformation. + */ + +void EnableBatteryNotifications() { + if (!MacPowerInformationService::IsShuttingDown()) { + MacPowerInformationService::GetInstance()->BeginListening(); + } +} + +void DisableBatteryNotifications() { + if (!MacPowerInformationService::IsShuttingDown()) { + MacPowerInformationService::GetInstance()->StopListening(); + } +} + +void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) { + MacPowerInformationService* powerService = + MacPowerInformationService::GetInstance(); + + aBatteryInfo->level() = powerService->mLevel; + aBatteryInfo->charging() = powerService->mCharging; + aBatteryInfo->remainingTime() = powerService->mRemainingTime; +} + +bool MacPowerInformationService::sShuttingDown = false; + +/* + * Following is the implementation of MacPowerInformationService. + */ + +MacPowerInformationService* MacPowerInformationService::sInstance = nullptr; + +namespace { +struct SingletonDestroyer final : public nsIObserver { + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + ~SingletonDestroyer() {} +}; + +NS_IMPL_ISUPPORTS(SingletonDestroyer, nsIObserver) + +NS_IMETHODIMP +SingletonDestroyer::Observe(nsISupports*, const char* aTopic, const char16_t*) { + MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown")); + MacPowerInformationService::Shutdown(); + return NS_OK; +} +} // namespace + +/* static */ +MacPowerInformationService* MacPowerInformationService::GetInstance() { + if (sInstance) { + return sInstance; + } + + sInstance = new MacPowerInformationService(); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false); + } + + return sInstance; +} + +bool MacPowerInformationService::IsShuttingDown() { return sShuttingDown; } + +void MacPowerInformationService::Shutdown() { + sShuttingDown = true; + delete sInstance; + sInstance = nullptr; +} + +MacPowerInformationService::MacPowerInformationService() + : mRunLoopSource(nullptr), + mLevel(kDefaultLevel), + mCharging(kDefaultCharging), + mRemainingTime(kDefaultRemainingTime), + mShouldNotify(false) { + // IOPSGetTimeRemainingEstimate (and the related constants) are only available + // on 10.7, so we test for their presence at runtime. + sIOKitFramework = dlopen(IOKIT_FRAMEWORK_PATH, RTLD_LAZY | RTLD_LOCAL); + if (sIOKitFramework) { + sIOPSGetTimeRemainingEstimate = (IOPSGetTimeRemainingEstimateFunc)dlsym( + sIOKitFramework, "IOPSGetTimeRemainingEstimate"); + } else { + sIOPSGetTimeRemainingEstimate = nullptr; + } +} + +MacPowerInformationService::~MacPowerInformationService() { + MOZ_ASSERT(!mRunLoopSource, + "The observers have not been correctly removed! " + "(StopListening should have been called)"); + + if (sIOKitFramework) { + dlclose(sIOKitFramework); + } +} + +void MacPowerInformationService::BeginListening() { + // Set ourselves up to be notified about changes. + MOZ_ASSERT(!mRunLoopSource, + "IOPS Notification Loop Source already set up. " + "(StopListening should have been called)"); + + mRunLoopSource = ::IOPSNotificationCreateRunLoopSource(HandleChange, this); + if (mRunLoopSource) { + ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mRunLoopSource, + kCFRunLoopDefaultMode); + + // Invoke our callback now so we have data if GetCurrentBatteryInformation + // is called before a change happens. + HandleChange(this); + mShouldNotify = true; + } +} + +void MacPowerInformationService::StopListening() { + if (mRunLoopSource) { + ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource, + kCFRunLoopDefaultMode); + mRunLoopSource = nullptr; + } +} + +void MacPowerInformationService::HandleChange(void* aContext) { + MacPowerInformationService* power = + static_cast<MacPowerInformationService*>(aContext); + + CFTypeRef data = ::IOPSCopyPowerSourcesInfo(); + if (!data) { + ::CFRelease(data); + return; + } + + // Get the list of power sources. + CFArrayRef list = ::IOPSCopyPowerSourcesList(data); + if (!list) { + ::CFRelease(list); + return; + } + + // Default values. These will be used if there are 0 sources or we can't find + // better information. + double level = kDefaultLevel; + double charging = kDefaultCharging; + double remainingTime = kDefaultRemainingTime; + + // Look for the first battery power source to give us the information we need. + // Usually there's only 1 available, depending on current power source. + for (CFIndex i = 0; i < ::CFArrayGetCount(list); ++i) { + CFTypeRef source = ::CFArrayGetValueAtIndex(list, i); + CFDictionaryRef currPowerSourceDesc = + ::IOPSGetPowerSourceDescription(data, source); + if (!currPowerSourceDesc) { + continue; + } + + // Get a battery level estimate. This key is required but does not always + // exist. + int currentCapacity = 0; + const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, + CFSTR(kIOPSCurrentCapacityKey)); + if (!cfRef) { + continue; + } + ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, + ¤tCapacity); + + // This key is also required. + int maxCapacity = 0; + cfRef = + ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSMaxCapacityKey)); + ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, &maxCapacity); + + if (maxCapacity > 0) { + level = static_cast<double>(currentCapacity) / + static_cast<double>(maxCapacity); + } + + // Find out if we're charging. + // This key is optional, we fallback to kDefaultCharging if the current + // power source doesn't have that info. + if (::CFDictionaryGetValueIfPresent(currPowerSourceDesc, + CFSTR(kIOPSIsChargingKey), &cfRef)) { + charging = ::CFBooleanGetValue((CFBooleanRef)cfRef); + + // Get an estimate of how long it's going to take until we're fully + // charged. This key is optional. + if (charging) { + // Default value that will be changed if we happen to find the actual + // remaining time. + remainingTime = + level == 1.0 ? kDefaultRemainingTime : kUnknownRemainingTime; + + if (::CFDictionaryGetValueIfPresent( + currPowerSourceDesc, CFSTR(kIOPSTimeToFullChargeKey), &cfRef)) { + int timeToCharge; + ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType, + &timeToCharge); + if (timeToCharge != kIOPSTimeRemainingUnknown) { + remainingTime = timeToCharge * 60; + } + } + } else if (sIOPSGetTimeRemainingEstimate) { // not charging + // See if we can get a time estimate. + CFTimeInterval estimate = sIOPSGetTimeRemainingEstimate(); + if (estimate == kIOPSTimeRemainingUnlimited || + estimate == kIOPSTimeRemainingUnknown) { + remainingTime = kUnknownRemainingTime; + } else { + remainingTime = estimate; + } + } + } + + break; + } + + bool isNewData = level != power->mLevel || charging != power->mCharging || + remainingTime != power->mRemainingTime; + + power->mRemainingTime = remainingTime; + power->mCharging = charging; + power->mLevel = level; + + // Notify the observers if stuff changed. + if (power->mShouldNotify && isNewData) { + hal::NotifyBatteryChange(hal::BatteryInformation( + power->mLevel, power->mCharging, power->mRemainingTime)); + } + + ::CFRelease(data); + ::CFRelease(list); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/cocoa/CocoaHeterogeneousCpuInfo.cpp b/hal/cocoa/CocoaHeterogeneousCpuInfo.cpp new file mode 100644 index 0000000000..065a25b930 --- /dev/null +++ b/hal/cocoa/CocoaHeterogeneousCpuInfo.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalTypes.h" +#include <sys/types.h> +#include <sys/sysctl.h> +#include "mozilla/BitSet.h" + +namespace mozilla::hal_impl { + +mozilla::Maybe<hal::HeterogeneousCpuInfo> CreateHeterogeneousCpuInfo() { +#ifdef __aarch64__ + // As of now on Apple Silicon the number of *.logicalcpu_max is the same as + // *.physicalcpu_max. + size_t len = sizeof(uint32_t); + uint32_t pCores = 0; + if (sysctlbyname("hw.perflevel0.logicalcpu_max", &pCores, &len, nullptr, 0)) { + return Nothing(); + } + + len = sizeof(uint32_t); + uint32_t eCores = 0; + if (sysctlbyname("hw.perflevel1.logicalcpu_max", &eCores, &len, nullptr, 0)) { + return Nothing(); + } + + hal::HeterogeneousCpuInfo info; + info.mTotalNumCpus = pCores + eCores; + + // The API has currently a limit how many cpu cores it can tell about. + for (uint32_t i = 0; i < hal::HeterogeneousCpuInfo::MAX_CPUS; ++i) { + if (pCores) { + --pCores; + info.mBigCpus[i] = true; + } else if (eCores) { + --eCores; + info.mLittleCpus[i] = true; + } else { + break; + } + } + + return Some(info); +#else + return Nothing(); +#endif +} + +const Maybe<hal::HeterogeneousCpuInfo>& GetHeterogeneousCpuInfo() { + static const Maybe<hal::HeterogeneousCpuInfo> cpuInfo = + CreateHeterogeneousCpuInfo(); + return cpuInfo; +} + +} // namespace mozilla::hal_impl diff --git a/hal/cocoa/CocoaSensor.mm b/hal/cocoa/CocoaSensor.mm new file mode 100644 index 0000000000..ecc39272dc --- /dev/null +++ b/hal/cocoa/CocoaSensor.mm @@ -0,0 +1,140 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "Hal.h" +#include "nsITimer.h" +#include "smslib.h" +#include "nsComponentManagerUtils.h" + +#include <mach/mach.h> +#include <cmath> +#import <IOKit/IOKitLib.h> + +#define MEAN_GRAVITY 9.80665 +#define DEFAULT_SENSOR_POLL 100 +using namespace mozilla::hal; +namespace mozilla { +namespace hal_impl { +static nsITimer* sUpdateTimer = nullptr; +static bool sActiveSensors[NUM_SENSOR_TYPE]; +static io_connect_t sDataPort = IO_OBJECT_NULL; +static uint64_t sLastMean = -1; +static float LMUvalueToLux(uint64_t aValue) { + // Conversion formula from regression. See Bug 793728. + // -3*(10^-27)*x^4 + 2.6*(10^-19)*x^3 + -3.4*(10^-12)*x^2 + 3.9*(10^-5)*x - + // 0.19 + long double powerC4 = 1 / pow((long double)10, 27); + long double powerC3 = 1 / pow((long double)10, 19); + long double powerC2 = 1 / pow((long double)10, 12); + long double powerC1 = 1 / pow((long double)10, 5); + + long double term4 = -3.0 * powerC4 * pow(aValue, 4); + long double term3 = 2.6 * powerC3 * pow(aValue, 3); + long double term2 = -3.4 * powerC2 * pow(aValue, 2); + long double term1 = 3.9 * powerC1 * aValue; + + float lux = ceil(static_cast<float>(term4 + term3 + term2 + term1 - 0.19)); + return lux > 0 ? lux : 0; +} +void UpdateHandler(nsITimer* aTimer, void* aClosure) { + for (int i = 0; i < NUM_SENSOR_TYPE; i++) { + if (!sActiveSensors[i]) { + continue; + } + SensorType sensor = static_cast<SensorType>(i); + nsTArray<float> values; + if (sensor == SENSOR_ACCELERATION) { + sms_acceleration accel; + smsGetData(&accel); + + values.AppendElement(accel.x * MEAN_GRAVITY); + values.AppendElement(accel.y * MEAN_GRAVITY); + values.AppendElement(accel.z * MEAN_GRAVITY); + } else if (sensor == SENSOR_LIGHT && sDataPort != IO_OBJECT_NULL) { + kern_return_t kr; + uint32_t outputs = 2; + uint64_t lightLMU[outputs]; + + kr = IOConnectCallMethod(sDataPort, 0, nil, 0, nil, 0, lightLMU, &outputs, + nil, 0); + if (kr == KERN_SUCCESS) { + uint64_t mean = (lightLMU[0] + lightLMU[1]) / 2; + if (mean == sLastMean) { + continue; + } + sLastMean = mean; + values.AppendElement(LMUvalueToLux(mean)); + } else if (kr == kIOReturnBusy) { + continue; + } + } + + hal::SensorData sdata(sensor, PR_Now(), values); + hal::NotifySensorChange(sdata); + } +} +void EnableSensorNotifications(SensorType aSensor) { + if (aSensor == SENSOR_ACCELERATION) { + int result = smsStartup(nil, nil); + + if (result != SMS_SUCCESS) { + return; + } + + if (!smsLoadCalibration()) { + return; + } + } else if (aSensor == SENSOR_LIGHT) { + io_service_t serviceObject; + serviceObject = IOServiceGetMatchingService( + kIOMasterPortDefault, IOServiceMatching("AppleLMUController")); + if (!serviceObject) { + return; + } + kern_return_t kr; + kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &sDataPort); + IOObjectRelease(serviceObject); + if (kr != KERN_SUCCESS) { + return; + } + } else { + NS_WARNING("EnableSensorNotifications called on an unknown sensor type"); + return; + } + sActiveSensors[aSensor] = true; + + if (!sUpdateTimer) { + CallCreateInstance("@mozilla.org/timer;1", &sUpdateTimer); + if (sUpdateTimer) { + sUpdateTimer->InitWithNamedFuncCallback( + UpdateHandler, nullptr, DEFAULT_SENSOR_POLL, + nsITimer::TYPE_REPEATING_SLACK, "hal_impl::UpdateHandler"); + } + } +} +void DisableSensorNotifications(SensorType aSensor) { + if (!sActiveSensors[aSensor] || + (aSensor != SENSOR_ACCELERATION && aSensor != SENSOR_LIGHT)) { + return; + } + + sActiveSensors[aSensor] = false; + + if (aSensor == SENSOR_ACCELERATION) { + smsShutdown(); + } else if (aSensor == SENSOR_LIGHT) { + IOServiceClose(sDataPort); + } + // If all sensors are disabled, cancel the update timer. + if (sUpdateTimer) { + for (int i = 0; i < NUM_SENSOR_TYPE; i++) { + if (sActiveSensors[i]) { + return; + } + } + sUpdateTimer->Cancel(); + NS_RELEASE(sUpdateTimer); + } +} +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/cocoa/smslib.h b/hal/cocoa/smslib.h new file mode 100644 index 0000000000..cf1ee8cb3f --- /dev/null +++ b/hal/cocoa/smslib.h @@ -0,0 +1,158 @@ +/* + * smslib.h + * + * SMSLib Sudden Motion Sensor Access Library + * Copyright (c) 2010 Suitable Systems + * All rights reserved. + * + * Developed by: Daniel Griscom + * Suitable Systems + * http://www.suitable.com + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal with the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimers. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimers in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the names of Suitable Systems nor the names of its + * contributors may be used to endorse or promote products derived from + * this Software without specific prior written permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. + * + * For more information about SMSLib, see + * <http://www.suitable.com/tools/smslib.html> + * or contact + * Daniel Griscom + * Suitable Systems + * 1 Centre Street, Suite 204 + * Wakefield, MA 01880 + * (781) 665-0053 + * + */ + +#import <Foundation/Foundation.h> + +#define SMSLIB_VERSION "1.8" + +#pragma mark Structure definitions + +// Structure for specifying a 3-axis acceleration. 0.0 means "zero gravities", +// 1.0 means "one gravity". +typedef struct sms_acceleration { + float x; // Right-left acceleration (positive is rightwards) + float y; // Front-rear acceleration (positive is rearwards) + float z; // Up-down acceleration (positive is upwards) +} sms_acceleration; + +// Structure for specifying a calibration. +typedef struct sms_calibration { + float zeros[3]; // Zero points for three axes (X, Y, Z) + float onegs[3]; // One gravity values for three axes +} sms_calibration; + +#pragma mark Return value definitions + +// These are the return values for accelStartup(), giving the +// various stages where the most successful attempt at accessing +// the accelerometer failed. The higher the value, the further along the +// software progressed before failing. The options are: +// - Didn't match model name +#define SMS_FAIL_MODEL (-7) +// - Failure getting dictionary matching desired services +#define SMS_FAIL_DICTIONARY (-6) +// - Failure getting list of services +#define SMS_FAIL_LIST_SERVICES (-5) +// - Failure if list of services is empty. The process generally fails +// here if run on a machine without a Sudden Motion Sensor. +#define SMS_FAIL_NO_SERVICES (-4) +// - Failure if error opening device. +#define SMS_FAIL_OPENING (-3) +// - Failure if opened, but didn't get a connection +#define SMS_FAIL_CONNECTION (-2) +// - Failure if couldn't access connction using given function and size. +// This is where the process would probably fail with a change +// in Apple's API. Driver problems often also cause failures here. +#define SMS_FAIL_ACCESS (-1) +// - Success! +#define SMS_SUCCESS (0) + +#pragma mark Function declarations + +// This starts up the accelerometer code, trying each possible sensor +// specification. Note that for logging purposes it +// takes an object and a selector; the object's selector is then invoked +// with a single NSString as argument giving progress messages. Example +// logging method: +// - (void)logMessage: (NSString *)theString +// which would be used in accelStartup's invocation thusly: +// result = accelStartup(self, @selector(logMessage:)); +// If the object is nil, then no logging is done. Sets calibation from built-in +// value table. Returns ACCEL_SUCCESS for success, and other (negative) +// values for various failures (returns value indicating result of +// most successful trial). +int smsStartup(id logObject, SEL logSelector); + +// This starts up the library in debug mode, ignoring the actual hardware. +// Returned data is in the form of 1Hz sine waves, with the X, Y and Z +// axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5); +// "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0, +// Z axes centered on 1 (calibrated) or 256 (uncalibrated). +// Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS. +int smsDebugStartup(id logObject, SEL logSelector); + +// Returns the current calibration values. +void smsGetCalibration(sms_calibration* calibrationRecord); + +// Sets the calibration, but does NOT store it as a preference. If the argument +// is nil then the current calibration is set from the built-in value table. +void smsSetCalibration(sms_calibration* calibrationRecord); + +// Stores the current calibration values as a stored preference. +void smsStoreCalibration(void); + +// Loads the stored preference values into the current calibration. +// Returns YES if successful. +BOOL smsLoadCalibration(void); + +// Deletes any stored calibration, and then takes the current calibration values +// from the built-in value table. +void smsDeleteCalibration(void); + +// Fills in the accel record with calibrated acceleration data. Takes +// 1-2ms to return a value. Returns 0 if success, error number if failure. +int smsGetData(sms_acceleration* accel); + +// Fills in the accel record with uncalibrated acceleration data. +// Returns 0 if success, error number if failure. +int smsGetUncalibratedData(sms_acceleration* accel); + +// Returns the length of a raw block of data for the current type of sensor. +int smsGetBufferLength(void); + +// Takes a pointer to accelGetRawLength() bytes; sets those bytes +// to return value from sensor. Make darn sure the buffer length is right! +void smsGetBufferData(char* buffer); + +// This returns an NSString describing the current calibration in +// human-readable form. Also include a description of the machine. +NSString* smsGetCalibrationDescription(void); + +// Shuts down the accelerometer. +void smsShutdown(void); diff --git a/hal/cocoa/smslib.mm b/hal/cocoa/smslib.mm new file mode 100644 index 0000000000..bd72b80032 --- /dev/null +++ b/hal/cocoa/smslib.mm @@ -0,0 +1,906 @@ +/* + * smslib.m + * + * SMSLib Sudden Motion Sensor Access Library + * Copyright (c) 2010 Suitable Systems + * All rights reserved. + * + * Developed by: Daniel Griscom + * Suitable Systems + * http://www.suitable.com + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal with the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimers. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimers in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the names of Suitable Systems nor the names of its + * contributors may be used to endorse or promote products derived from + * this Software without specific prior written permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. + * + * For more information about SMSLib, see + * <http://www.suitable.com/tools/smslib.html> + * or contact + * Daniel Griscom + * Suitable Systems + * 1 Centre Street, Suite 204 + * Wakefield, MA 01880 + * (781) 665-0053 + * + */ + +#import <IOKit/IOKitLib.h> +#import <sys/sysctl.h> +#import <math.h> +#import "smslib.h" + +#pragma mark Internal structures + +// Represents a single axis of a type of sensor. +typedef struct axisStruct { + int enabled; // Non-zero if axis is valid in this sensor + int index; // Location in struct of first byte + int size; // Number of bytes + float zerog; // Value meaning "zero g" + float oneg; // Change in value meaning "increase of one g" + // (can be negative if axis sensor reversed) +} axisStruct; + +// Represents the configuration of a type of sensor. +typedef struct sensorSpec { + const char* model; // Prefix of model to be tested + const char* name; // Name of device to be read + unsigned int function; // Kernel function index + int recordSize; // Size of record to be sent/received + axisStruct axes[3]; // Description of three axes (X, Y, Z) +} sensorSpec; + +// Configuration of all known types of sensors. The configurations are +// tried in order until one succeeds in returning data. +// All default values are set here, but each axis' zerog and oneg values +// may be changed to saved (calibrated) values. +// +// These values came from SeisMaCalibrate calibration reports. In general I've +// found the following: +// - All Intel-based SMSs have 250 counts per g, centered on 0, but the signs +// are different (and in one case two axes are swapped) +// - PowerBooks and iBooks all have sensors centered on 0, and reading 50-53 +// steps per gravity (but with differing polarities!) +// - PowerBooks and iBooks of the same model all have the same axis polarities +// - PowerBook and iBook access methods are model- and OS version-specific +// +// So, the sequence of tests is: +// - Try model-specific access methods. Note that the test is for a match to the +// beginning of the model name, e.g. the record with model name "MacBook" +// matches computer models "MacBookPro1,2" and "MacBook1,1" (and "" matches +// any model). +// - If no model-specific record's access fails, then try each model-independent +// method in order, stopping when one works. +static const sensorSpec sensors[] = { + // ****** Model-dependent methods ****** + // The PowerBook5,6 is one of the G4 models that seems to lose + // SMS access until the next reboot. + {"PowerBook5,6", + "IOI2CMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, -51.5}, {1, 2, 1, 0, -51.5}}}, + // The PowerBook5,7 is one of the G4 models that seems to lose + // SMS access until the next reboot. + {"PowerBook5,7", + "IOI2CMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}}, + // Access seems to be reliable on the PowerBook5,8 + {"PowerBook5,8", + "PMUMotionSensor", + 21, + 60, + {{1, 0, 1, 0, -51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, -51.5}}}, + // Access seems to be reliable on the PowerBook5,9 + {"PowerBook5,9", + "PMUMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, -51.5}, {1, 2, 1, 0, -51.5}}}, + // The PowerBook6,7 is one of the G4 models that seems to lose + // SMS access until the next reboot. + {"PowerBook6,7", + "IOI2CMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}}, + // The PowerBook6,8 is one of the G4 models that seems to lose + // SMS access until the next reboot. + {"PowerBook6,8", + "IOI2CMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}}, + // MacBook Pro Core 2 Duo 17". Note the reversed Y and Z axes. + {"MacBookPro2,1", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, 251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, -251}}}, + // MacBook Pro Core 2 Duo 15" AND 17" with LED backlight, introduced June + // '07. + // NOTE! The 17" machines have the signs of their X and Y axes reversed + // from this calibration, but there's no clear way to discriminate between + // the two machines. + {"MacBookPro3,1", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251}}}, + // ... specs? + {"MacBook5,2", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, -251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, -251}}}, + // ... specs? + {"MacBookPro5,1", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}}, + // ... specs? + {"MacBookPro5,2", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}}, + // This is speculative, based on a single user's report. Looks like the X + // and Y axes + // are swapped. This is true for no other known Appple laptop. + {"MacBookPro5,3", + "SMCMotionSensor", + 5, + 40, + {{1, 2, 2, 0, -251}, {1, 0, 2, 0, -251}, {1, 4, 2, 0, -251}}}, + // ... specs? + {"MacBookPro5,4", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, -251}, {1, 2, 2, 0, -251}, {1, 4, 2, 0, 251}}}, + // ****** Model-independent methods ****** + // Seen once with PowerBook6,8 under system 10.3.9; I suspect + // other G4-based 10.3.* systems might use this + {"", + "IOI2CMotionSensor", + 24, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}}, + // PowerBook5,6 , PowerBook5,7 , PowerBook6,7 , PowerBook6,8 + // under OS X 10.4.* + {"", + "IOI2CMotionSensor", + 21, + 60, + {{1, 0, 1, 0, 51.5}, {1, 1, 1, 0, 51.5}, {1, 2, 1, 0, 51.5}}}, + // PowerBook5,8 , PowerBook5,9 under OS X 10.4.* + {"", + "PMUMotionSensor", + 21, + 60, + {// Each has two out of three gains negative, but it's different + // for the different models. So, this will be right in two out + // of three axis for either model. + {1, 0, 1, 0, -51.5}, + {1, 1, 1, -6, -51.5}, + {1, 2, 1, 0, -51.5}}}, + // All MacBook, MacBookPro models. Hardware (at least on early MacBookPro + // 15") + // is Kionix KXM52-1050 three-axis accelerometer chip. Data is at + // http://kionix.com/Product-Index/product-index.htm. Specific MB and MBP + // models + // that use this are: + // MacBook1,1 + // MacBook2,1 + // MacBook3,1 + // MacBook4,1 + // MacBook5,1 + // MacBook6,1 + // MacBookAir1,1 + // MacBookPro1,1 + // MacBookPro1,2 + // MacBookPro4,1 + // MacBookPro5,5 + {"", + "SMCMotionSensor", + 5, + 40, + {{1, 0, 2, 0, 251}, {1, 2, 2, 0, 251}, {1, 4, 2, 0, 251}}}}; + +#define SENSOR_COUNT (sizeof(sensors) / sizeof(sensorSpec)) + +#pragma mark Internal prototypes + +static int getData(sms_acceleration* accel, int calibrated, id logObject, + SEL logSelector); +static float getAxis(int which, int calibrated); +static int signExtend(int value, int size); +static NSString* getModelName(void); +static NSString* getOSVersion(void); +static BOOL loadCalibration(void); +static void storeCalibration(void); +static void defaultCalibration(void); +static void deleteCalibration(void); +static int prefIntRead(NSString* prefName, BOOL* success); +static void prefIntWrite(NSString* prefName, int prefValue); +static float prefFloatRead(NSString* prefName, BOOL* success); +static void prefFloatWrite(NSString* prefName, float prefValue); +static void prefDelete(NSString* prefName); +static void prefSynchronize(void); +// static long getMicroseconds(void); +float fakeData(NSTimeInterval time); + +#pragma mark Static variables + +static int debugging = NO; // True if debugging (synthetic data) +static io_connect_t connection; // Connection for reading accel values +static int running = NO; // True if we successfully started +static unsigned int sensorNum = 0; // The current index into sensors[] +static const char* serviceName; // The name of the current service +static char *iRecord, *oRecord; // Pointers to read/write records for sensor +static int recordSize; // Size of read/write records +static unsigned int function; // Which kernel function should be used +static float zeros[3]; // X, Y and Z zero calibration values +static float onegs[3]; // X, Y and Z one-g calibration values + +#pragma mark Defines + +// Pattern for building axis letter from axis number +#define INT_TO_AXIS(a) (a == 0 ? @"X" : a == 1 ? @"Y" : @"Z") +// Name of configuration for given axis' zero (axis specified by integer) +#define ZERO_NAME(a) [NSString stringWithFormat:@"%@-Axis-Zero", INT_TO_AXIS(a)] +// Name of configuration for given axis' oneg (axis specified by integer) +#define ONEG_NAME(a) \ + [NSString stringWithFormat:@"%@-Axis-One-g", INT_TO_AXIS(a)] +// Name of "Is calibrated" preference +#define CALIBRATED_NAME (@"Calibrated") +// Application domain for SeisMac library +#define APP_ID ((CFStringRef) @"com.suitable.SeisMacLib") + +// These #defines make the accelStartup code a LOT easier to read. +#undef LOG +#define LOG(message) \ + if (logObject) { \ + [logObject performSelector:logSelector withObject:message]; \ + } +#define LOG_ARG(format, var1) \ + if (logObject) { \ + [logObject performSelector:logSelector \ + withObject:[NSString stringWithFormat:format, var1]]; \ + } +#define LOG_2ARG(format, var1, var2) \ + if (logObject) { \ + [logObject \ + performSelector:logSelector \ + withObject:[NSString stringWithFormat:format, var1, var2]]; \ + } +#define LOG_3ARG(format, var1, var2, var3) \ + if (logObject) { \ + [logObject \ + performSelector:logSelector \ + withObject:[NSString stringWithFormat:format, var1, var2, var3]]; \ + } + +#pragma mark Function definitions + +// This starts up the accelerometer code, trying each possible sensor +// specification. Note that for logging purposes it +// takes an object and a selector; the object's selector is then invoked +// with a single NSString as argument giving progress messages. Example +// logging method: +// - (void)logMessage: (NSString *)theString +// which would be used in accelStartup's invocation thusly: +// result = accelStartup(self, @selector(logMessage:)); +// If the object is nil, then no logging is done. Sets calibation from built-in +// value table. Returns ACCEL_SUCCESS for success, and other (negative) +// values for various failures (returns value indicating result of +// most successful trial). +int smsStartup(id logObject, SEL logSelector) { + io_iterator_t iterator; + io_object_t device; + kern_return_t result; + sms_acceleration accel; + int failure_result = SMS_FAIL_MODEL; + + running = NO; + debugging = NO; + + NSString* modelName = getModelName(); + + LOG_ARG(@"Machine model: %@\n", modelName); + LOG_ARG(@"OS X version: %@\n", getOSVersion()); + LOG_ARG(@"Accelerometer library version: %s\n", SMSLIB_VERSION); + + for (sensorNum = 0; sensorNum < SENSOR_COUNT; sensorNum++) { + // Set up all specs for this type of sensor + serviceName = sensors[sensorNum].name; + recordSize = sensors[sensorNum].recordSize; + function = sensors[sensorNum].function; + + LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n", + serviceName, function, recordSize); + + NSString* targetName = + [NSString stringWithCString:sensors[sensorNum].model + encoding:NSMacOSRomanStringEncoding]; + LOG_ARG(@" Comparing model name to target \"%@\": ", targetName); + if ([targetName length] == 0 || [modelName hasPrefix:targetName]) { + LOG(@"success.\n"); + } else { + LOG(@"failure.\n"); + // Don't need to increment failure_result. + continue; + } + + LOG(@" Fetching dictionary for service: "); + CFMutableDictionaryRef dict = IOServiceMatching(serviceName); + + if (dict) { + LOG(@"success.\n"); + } else { + LOG(@"failure.\n"); + if (failure_result < SMS_FAIL_DICTIONARY) { + failure_result = SMS_FAIL_DICTIONARY; + } + continue; + } + + LOG(@" Getting list of matching services: "); + result = + IOServiceGetMatchingServices(kIOMasterPortDefault, dict, &iterator); + + if (result == KERN_SUCCESS) { + LOG(@"success.\n"); + } else { + LOG_ARG(@"failure, with return value 0x%x.\n", result); + if (failure_result < SMS_FAIL_LIST_SERVICES) { + failure_result = SMS_FAIL_LIST_SERVICES; + } + continue; + } + + LOG(@" Getting first device in list: "); + device = IOIteratorNext(iterator); + + if (device == 0) { + LOG(@"failure.\n"); + if (failure_result < SMS_FAIL_NO_SERVICES) { + failure_result = SMS_FAIL_NO_SERVICES; + } + continue; + } else { + LOG(@"success.\n"); + LOG(@" Opening device: "); + } + + result = IOServiceOpen(device, mach_task_self(), 0, &connection); + + if (result != KERN_SUCCESS) { + LOG_ARG(@"failure, with return value 0x%x.\n", result); + IOObjectRelease(device); + if (failure_result < SMS_FAIL_OPENING) { + failure_result = SMS_FAIL_OPENING; + } + continue; + } else if (connection == 0) { + LOG_ARG( + @"'success', but didn't get a connection (return value was: 0x%x).\n", + result); + IOObjectRelease(device); + if (failure_result < SMS_FAIL_CONNECTION) { + failure_result = SMS_FAIL_CONNECTION; + } + continue; + } else { + IOObjectRelease(device); + LOG(@"success.\n"); + } + LOG(@" Testing device.\n"); + + defaultCalibration(); + + iRecord = (char*)malloc(recordSize); + oRecord = (char*)malloc(recordSize); + + running = YES; + result = getData(&accel, true, logObject, logSelector); + running = NO; + + if (result) { + LOG_ARG(@" Failure testing device, with result 0x%x.\n", result); + free(iRecord); + iRecord = 0; + free(oRecord); + oRecord = 0; + if (failure_result < SMS_FAIL_ACCESS) { + failure_result = SMS_FAIL_ACCESS; + } + continue; + } else { + LOG(@" Success testing device!\n"); + running = YES; + return SMS_SUCCESS; + } + } + return failure_result; +} + +// This starts up the library in debug mode, ignoring the actual hardware. +// Returned data is in the form of 1Hz sine waves, with the X, Y and Z +// axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5); +// "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0, +// Z axes centered on 1 (calibrated) or 256 (uncalibrated). +// Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS. +int smsDebugStartup(id logObject, SEL logSelector) { + LOG(@"Starting up in debug mode\n"); + debugging = YES; + return SMS_SUCCESS; +} + +// Returns the current calibration values. +void smsGetCalibration(sms_calibration* calibrationRecord) { + int x; + + for (x = 0; x < 3; x++) { + calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]); + calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]); + } +} + +// Sets the calibration, but does NOT store it as a preference. If the argument +// is nil then the current calibration is set from the built-in value table. +void smsSetCalibration(sms_calibration* calibrationRecord) { + int x; + + if (!debugging) { + if (calibrationRecord) { + for (x = 0; x < 3; x++) { + zeros[x] = calibrationRecord->zeros[x]; + onegs[x] = calibrationRecord->onegs[x]; + } + } else { + defaultCalibration(); + } + } +} + +// Stores the current calibration values as a stored preference. +void smsStoreCalibration(void) { + if (!debugging) storeCalibration(); +} + +// Loads the stored preference values into the current calibration. +// Returns YES if successful. +BOOL smsLoadCalibration(void) { + if (debugging) { + return YES; + } else if (loadCalibration()) { + return YES; + } else { + defaultCalibration(); + return NO; + } +} + +// Deletes any stored calibration, and then takes the current calibration values +// from the built-in value table. +void smsDeleteCalibration(void) { + if (!debugging) { + deleteCalibration(); + defaultCalibration(); + } +} + +// Fills in the accel record with calibrated acceleration data. Takes +// 1-2ms to return a value. Returns 0 if success, error number if failure. +int smsGetData(sms_acceleration* accel) { + NSTimeInterval time; + if (debugging) { + usleep(1500); // Usually takes 1-2 milliseconds + time = [NSDate timeIntervalSinceReferenceDate]; + accel->x = fakeData(time) / 5; + accel->y = fakeData(time - 1) / 5; + accel->z = fakeData(time - 2) / 5 + 1.0; + return true; + } else { + return getData(accel, true, nil, nil); + } +} + +// Fills in the accel record with uncalibrated acceleration data. +// Returns 0 if success, error number if failure. +int smsGetUncalibratedData(sms_acceleration* accel) { + NSTimeInterval time; + if (debugging) { + usleep(1500); // Usually takes 1-2 milliseconds + time = [NSDate timeIntervalSinceReferenceDate]; + accel->x = fakeData(time) * 256 / 5; + accel->y = fakeData(time - 1) * 256 / 5; + accel->z = fakeData(time - 2) * 256 / 5 + 256; + return true; + } else { + return getData(accel, false, nil, nil); + } +} + +// Returns the length of a raw block of data for the current type of sensor. +int smsGetBufferLength(void) { + if (debugging) { + return 0; + } else if (running) { + return sensors[sensorNum].recordSize; + } else { + return 0; + } +} + +// Takes a pointer to accelGetRawLength() bytes; sets those bytes +// to return value from sensor. Make darn sure the buffer length is right! +void smsGetBufferData(char* buffer) { + IOItemCount iSize = recordSize; + IOByteCount oSize = recordSize; + kern_return_t result; + + if (debugging || running == NO) { + return; + } + + memset(iRecord, 1, iSize); + memset(buffer, 0, oSize); +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + const size_t InStructSize = recordSize; + size_t OutStructSize = recordSize; + result = IOConnectCallStructMethod(connection, + function, // magic kernel function number + (const void*)iRecord, InStructSize, + (void*)buffer, &OutStructSize); +#else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 + result = IOConnectMethodStructureIStructureO( + connection, + function, // magic kernel function number + iSize, &oSize, iRecord, buffer); +#endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 + + if (result != KERN_SUCCESS) { + running = NO; + } +} + +// This returns an NSString describing the current calibration in +// human-readable form. Also include a description of the machine. +NSString* smsGetCalibrationDescription(void) { + BOOL success; + NSMutableString* s = [[NSMutableString alloc] init]; + + if (debugging) { + [s release]; + return @"Debugging!"; + } + + [s appendString:@"---- SeisMac Calibration Record ----\n \n"]; + [s appendFormat:@"Machine model: %@\n", getModelName()]; + [s appendFormat:@"OS X build: %@\n", getOSVersion()]; + [s appendFormat:@"SeisMacLib version %s, record %d\n \n", SMSLIB_VERSION, + sensorNum]; + [s appendFormat:@"Using service \"%s\", function index %d, size %d\n \n", + serviceName, function, recordSize]; + if (prefIntRead(CALIBRATED_NAME, &success) && success) { + [s appendString:@"Calibration values (from calibration):\n"]; + } else { + [s appendString:@"Calibration values (from defaults):\n"]; + } + [s appendFormat:@" X-Axis-Zero = %.2f\n", zeros[0]]; + [s appendFormat:@" X-Axis-One-g = %.2f\n", onegs[0]]; + [s appendFormat:@" Y-Axis-Zero = %.2f\n", zeros[1]]; + [s appendFormat:@" Y-Axis-One-g = %.2f\n", onegs[1]]; + [s appendFormat:@" Z-Axis-Zero = %.2f\n", zeros[2]]; + [s appendFormat:@" Z-Axis-One-g = %.2f\n \n", onegs[2]]; + [s appendString:@"---- End Record ----\n"]; + return s; +} + +// Shuts down the accelerometer. +void smsShutdown(void) { + if (!debugging) { + running = NO; + if (iRecord) free(iRecord); + if (oRecord) free(oRecord); + IOServiceClose(connection); + } +} + +#pragma mark Internal functions + +// Loads the current calibration from the stored preferences. +// Returns true iff successful. +BOOL loadCalibration(void) { + BOOL thisSuccess, allSuccess; + int x; + + prefSynchronize(); + + if (prefIntRead(CALIBRATED_NAME, &thisSuccess) && thisSuccess) { + // Calibrated. Set all values from saved values. + allSuccess = YES; + for (x = 0; x < 3; x++) { + zeros[x] = prefFloatRead(ZERO_NAME(x), &thisSuccess); + allSuccess &= thisSuccess; + onegs[x] = prefFloatRead(ONEG_NAME(x), &thisSuccess); + allSuccess &= thisSuccess; + } + return allSuccess; + } + + return NO; +} + +// Stores the current calibration into the stored preferences. +static void storeCalibration(void) { + int x; + prefIntWrite(CALIBRATED_NAME, 1); + for (x = 0; x < 3; x++) { + prefFloatWrite(ZERO_NAME(x), zeros[x]); + prefFloatWrite(ONEG_NAME(x), onegs[x]); + } + prefSynchronize(); +} + +// Sets the calibration to its default values. +void defaultCalibration(void) { + int x; + for (x = 0; x < 3; x++) { + zeros[x] = sensors[sensorNum].axes[x].zerog; + onegs[x] = sensors[sensorNum].axes[x].oneg; + } +} + +// Deletes the stored preferences. +static void deleteCalibration(void) { + int x; + + prefDelete(CALIBRATED_NAME); + for (x = 0; x < 3; x++) { + prefDelete(ZERO_NAME(x)); + prefDelete(ONEG_NAME(x)); + } + prefSynchronize(); +} + +// Read a named floating point value from the stored preferences. Sets +// the success boolean based on, you guessed it, whether it succeeds. +static float prefFloatRead(NSString* prefName, BOOL* success) { + float result = 0.0f; + + CFPropertyListRef ref = + CFPreferencesCopyAppValue((CFStringRef)prefName, APP_ID); + // If there isn't such a preference, fail + if (ref == NULL) { + *success = NO; + return result; + } + CFTypeID typeID = CFGetTypeID(ref); + // Is it a number? + if (typeID == CFNumberGetTypeID()) { + // Is it a floating point number? + if (CFNumberIsFloatType((CFNumberRef)ref)) { + // Yup: grab it. + *success = + CFNumberGetValue((__CFNumber*)ref, kCFNumberFloat32Type, &result); + } else { + // Nope: grab as an integer, and convert to a float. + long num; + if (CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &num)) { + result = num; + *success = YES; + } else { + *success = NO; + } + } + // Or is it a string (e.g. set by the command line "defaults" command)? + } else if (typeID == CFStringGetTypeID()) { + result = (float)CFStringGetDoubleValue((CFStringRef)ref); + *success = YES; + } else { + // Can't convert to a number: fail. + *success = NO; + } + CFRelease(ref); + return result; +} + +// Writes a named floating point value to the stored preferences. +static void prefFloatWrite(NSString* prefName, float prefValue) { + CFNumberRef cfFloat = + CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &prefValue); + CFPreferencesSetAppValue((CFStringRef)prefName, cfFloat, APP_ID); + CFRelease(cfFloat); +} + +// Reads a named integer value from the stored preferences. +static int prefIntRead(NSString* prefName, BOOL* success) { + Boolean internalSuccess; + CFIndex result = CFPreferencesGetAppIntegerValue((CFStringRef)prefName, + APP_ID, &internalSuccess); + *success = internalSuccess; + + return result; +} + +// Writes a named integer value to the stored preferences. +static void prefIntWrite(NSString* prefName, int prefValue) { + CFPreferencesSetAppValue((CFStringRef)prefName, + (CFNumberRef)[NSNumber numberWithInt:prefValue], + APP_ID); +} + +// Deletes the named preference values. +static void prefDelete(NSString* prefName) { + CFPreferencesSetAppValue((CFStringRef)prefName, NULL, APP_ID); +} + +// Synchronizes the local preferences with the stored preferences. +static void prefSynchronize(void) { CFPreferencesAppSynchronize(APP_ID); } + +// Internal version of accelGetData, with logging +int getData(sms_acceleration* accel, int calibrated, id logObject, + SEL logSelector) { + IOItemCount iSize = recordSize; + IOByteCount oSize = recordSize; + kern_return_t result; + + if (running == NO) { + return -1; + } + + memset(iRecord, 1, iSize); + memset(oRecord, 0, oSize); + + LOG_2ARG(@" Querying device (%u, %d): ", sensors[sensorNum].function, + sensors[sensorNum].recordSize); + +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + const size_t InStructSize = recordSize; + size_t OutStructSize = recordSize; + result = IOConnectCallStructMethod(connection, + function, // magic kernel function number + (const void*)iRecord, InStructSize, + (void*)oRecord, &OutStructSize); +#else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 + result = IOConnectMethodStructureIStructureO( + connection, + function, // magic kernel function number + iSize, &oSize, iRecord, oRecord); +#endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050 + + if (result != KERN_SUCCESS) { + LOG(@"failed.\n"); + running = NO; + return result; + } else { + LOG(@"succeeded.\n"); + + accel->x = getAxis(0, calibrated); + accel->y = getAxis(1, calibrated); + accel->z = getAxis(2, calibrated); + return 0; + } +} + +// Given the returned record, extracts the value of the given axis. If +// calibrated, then zero G is 0.0, and one G is 1.0. +float getAxis(int which, int calibrated) { + // Get various values (to make code cleaner) + int indx = sensors[sensorNum].axes[which].index; + int size = sensors[sensorNum].axes[which].size; + float zerog = zeros[which]; + float oneg = onegs[which]; + // Storage for value to be returned + int value = 0; + + // Although the values in the returned record should have the proper + // endianness, we still have to get it into the proper end of value. +#if (BYTE_ORDER == BIG_ENDIAN) + // On PowerPC processors + memcpy(((char*)&value) + (sizeof(int) - size), &oRecord[indx], size); +#endif +#if (BYTE_ORDER == LITTLE_ENDIAN) + // On Intel processors + memcpy(&value, &oRecord[indx], size); +#endif + + value = signExtend(value, size); + + if (calibrated) { + // Scale and shift for zero. + return ((float)(value - zerog)) / oneg; + } else { + return value; + } +} + +// Extends the sign, given the length of the value. +int signExtend(int value, int size) { + // Extend sign + switch (size) { + case 1: + if (value & 0x00000080) value |= 0xffffff00; + break; + case 2: + if (value & 0x00008000) value |= 0xffff0000; + break; + case 3: + if (value & 0x00800000) value |= 0xff000000; + break; + } + return value; +} + +// Returns the model name of the computer (e.g. "MacBookPro1,1") +NSString* getModelName(void) { + char model[32]; + size_t len = sizeof(model); + int name[2] = {CTL_HW, HW_MODEL}; + NSString* result; + + if (sysctl(name, 2, &model, &len, NULL, 0) == 0) { + result = [NSString stringWithFormat:@"%s", model]; + } else { + result = @""; + } + + return result; +} + +// Returns the current OS X version and build (e.g. "10.4.7 (build 8J2135a)") +NSString* getOSVersion(void) { + NSDictionary* dict = + [NSDictionary dictionaryWithContentsOfFile: + @"/System/Library/CoreServices/SystemVersion.plist"]; + NSString* versionString = [dict objectForKey:@"ProductVersion"]; + NSString* buildString = [dict objectForKey:@"ProductBuildVersion"]; + NSString* wholeString = + [NSString stringWithFormat:@"%@ (build %@)", versionString, buildString]; + return wholeString; +} + +// Returns time within the current second in microseconds. +// long getMicroseconds() { +// struct timeval t; +// gettimeofday(&t, 0); +// return t.tv_usec; +//} + +// Returns fake data given the time. Range is +/-1. +float fakeData(NSTimeInterval time) { + long secs = lround(floor(time)); + int secsMod3 = secs % 3; + double angle = time * 10 * M_PI * 2; + double mag = exp(-(time - (secs - secsMod3)) * 2); + return sin(angle) * mag; +} diff --git a/hal/fallback/FallbackBattery.cpp b/hal/fallback/FallbackBattery.cpp new file mode 100644 index 0000000000..b4f2fe105a --- /dev/null +++ b/hal/fallback/FallbackBattery.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "mozilla/dom/battery/Constants.h" + +namespace mozilla { +namespace hal_impl { + +void EnableBatteryNotifications() {} + +void DisableBatteryNotifications() {} + +void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) { + aBatteryInfo->level() = dom::battery::kDefaultLevel; + aBatteryInfo->charging() = dom::battery::kDefaultCharging; + aBatteryInfo->remainingTime() = dom::battery::kDefaultRemainingTime; +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackHeterogeneousCpuInfo.cpp b/hal/fallback/FallbackHeterogeneousCpuInfo.cpp new file mode 100644 index 0000000000..649200e35c --- /dev/null +++ b/hal/fallback/FallbackHeterogeneousCpuInfo.cpp @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalTypes.h" + +namespace mozilla::hal_impl { + +const Maybe<hal::HeterogeneousCpuInfo>& GetHeterogeneousCpuInfo() { + static Maybe<hal::HeterogeneousCpuInfo> cpuInfo = Nothing(); + return cpuInfo; +} + +} // namespace mozilla::hal_impl diff --git a/hal/fallback/FallbackNetwork.cpp b/hal/fallback/FallbackNetwork.cpp new file mode 100644 index 0000000000..d944b3a579 --- /dev/null +++ b/hal/fallback/FallbackNetwork.cpp @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "mozilla/dom/network/Constants.h" + +namespace mozilla { +namespace hal_impl { + +void EnableNetworkNotifications() {} + +void DisableNetworkNotifications() {} + +void GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo) { + aNetworkInfo->type() = dom::network::kDefaultType; + aNetworkInfo->isWifi() = dom::network::kDefaultIsWifi; + aNetworkInfo->dhcpGateway() = dom::network::kDefaultDHCPGateway; +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackPerformanceHintManager.cpp b/hal/fallback/FallbackPerformanceHintManager.cpp new file mode 100644 index 0000000000..7686650113 --- /dev/null +++ b/hal/fallback/FallbackPerformanceHintManager.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" + +namespace mozilla { +namespace hal_impl { + +UniquePtr<hal::PerformanceHintSession> CreatePerformanceHintSession( + const nsTArray<PlatformThreadHandle>& aThreads, + mozilla::TimeDuration aTargetWorkDuration) { + return nullptr; +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackProcessPriority.cpp b/hal/fallback/FallbackProcessPriority.cpp new file mode 100644 index 0000000000..2d747c65e3 --- /dev/null +++ b/hal/fallback/FallbackProcessPriority.cpp @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalLog.h" + +using namespace mozilla::hal; + +namespace mozilla { +namespace hal_impl { + +void SetProcessPriority(int aPid, ProcessPriority aPriority) { + HAL_LOG("FallbackProcessPriority - SetProcessPriority(%d, %s)\n", aPid, + ProcessPriorityToString(aPriority)); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackScreenConfiguration.cpp b/hal/fallback/FallbackScreenConfiguration.cpp new file mode 100644 index 0000000000..e298855b90 --- /dev/null +++ b/hal/fallback/FallbackScreenConfiguration.cpp @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" + +namespace mozilla::hal_impl { + +RefPtr<GenericNonExclusivePromise> LockScreenOrientation( + const hal::ScreenOrientation& aOrientation) { + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_DOM_NOT_SUPPORTED_ERR, __func__); +} + +void UnlockScreenOrientation() {} + +} // namespace mozilla::hal_impl diff --git a/hal/fallback/FallbackSensor.cpp b/hal/fallback/FallbackSensor.cpp new file mode 100644 index 0000000000..2ce51904b7 --- /dev/null +++ b/hal/fallback/FallbackSensor.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Hal.h" + +using namespace mozilla::hal; + +namespace mozilla { +namespace hal_impl { + +void EnableSensorNotifications(SensorType aSensor) {} + +void DisableSensorNotifications(SensorType aSensor) {} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackVibration.cpp b/hal/fallback/FallbackVibration.cpp new file mode 100644 index 0000000000..f3b0302d03 --- /dev/null +++ b/hal/fallback/FallbackVibration.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" + +using mozilla::hal::WindowIdentifier; + +namespace mozilla { +namespace hal_impl { + +void Vibrate(const nsTArray<uint32_t>& pattern, hal::WindowIdentifier&&) {} + +void CancelVibrate(hal::WindowIdentifier&&) {} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/linux/LinuxProcessPriority.cpp b/hal/linux/LinuxProcessPriority.cpp new file mode 100644 index 0000000000..a0f72a4f38 --- /dev/null +++ b/hal/linux/LinuxProcessPriority.cpp @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalLog.h" + +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" + +#include <fcntl.h> +#include <unistd.h> + +using namespace mozilla::hal; + +namespace mozilla::hal_impl { + +/* The Linux OOM score is a value computed by the kernel ranging between 0 and + * 1000 and indicating how likely is a process to be killed when memory is + * tight. The larger the value the more likely a process is to be killed. The + * score is computed based on the amount of memory used plus an adjustment + * value. We chose our adjustment values to make it likely that processes are + * killed in the following order: + * + * 1. preallocated processes + * 2. background processes + * 3. background processes playing video (but no audio) + * 4. foreground processes or processes playing or recording audio + * 5. the parent process + * + * At the time of writing (2022) the base score for a process consuming very + * little memory seems to be around ~667. Our adjustments are thus designed + * to ensure this order starting from a 667 baseline but trying not to go too + * close to the 1000 limit where they would be clamped. */ + +const uint32_t kParentOomScoreAdjust = 0; +const uint32_t kForegroundOomScoreAdjust = 100; +const uint32_t kBackgroundPerceivableOomScoreAdjust = 133; +const uint32_t kBackgroundOomScoreAdjust = 167; +const uint32_t kPreallocOomScoreAdjust = 233; + +static uint32_t OomScoreAdjForPriority(ProcessPriority aPriority) { + switch (aPriority) { + case PROCESS_PRIORITY_BACKGROUND: + return kBackgroundOomScoreAdjust; + case PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE: + return kBackgroundPerceivableOomScoreAdjust; + case PROCESS_PRIORITY_PREALLOC: + return kPreallocOomScoreAdjust; + case PROCESS_PRIORITY_FOREGROUND: + return kForegroundOomScoreAdjust; + default: + return kParentOomScoreAdjust; + } +} + +void SetProcessPriority(int aPid, ProcessPriority aPriority) { + HAL_LOG("LinuxProcessPriority - SetProcessPriority(%d, %s)\n", aPid, + ProcessPriorityToString(aPriority)); + + uint32_t oomScoreAdj = OomScoreAdjForPriority(aPriority); + + char path[32] = {}; + SprintfLiteral(path, "/proc/%d/oom_score_adj", aPid); + + char oomScoreAdjStr[11] = {}; + SprintfLiteral(oomScoreAdjStr, "%d", oomScoreAdj); + + int fd = open(path, O_WRONLY); + if (fd < 0) { + return; + } + const size_t len = strlen(oomScoreAdjStr); + Unused << write(fd, oomScoreAdjStr, len); + Unused << close(fd); +} + +} // namespace mozilla::hal_impl diff --git a/hal/linux/UPowerClient.cpp b/hal/linux/UPowerClient.cpp new file mode 100644 index 0000000000..d5f5e1ca52 --- /dev/null +++ b/hal/linux/UPowerClient.cpp @@ -0,0 +1,412 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalLog.h" +#include <mozilla/Attributes.h> +#include <mozilla/dom/battery/Constants.h> +#include "mozilla/GRefPtr.h" +#include "mozilla/GUniquePtr.h" +#include <cmath> +#include <gio/gio.h> +#include "mozilla/widget/AsyncDBus.h" + +using namespace mozilla::widget; +using namespace mozilla::dom::battery; + +namespace mozilla::hal_impl { + +/** + * This is the declaration of UPowerClient class. This class is listening and + * communicating to upower daemon through DBus. + * There is no header file because this class shouldn't be public. + */ +class UPowerClient { + public: + static UPowerClient* GetInstance(); + + void BeginListening(); + void StopListening(); + + double GetLevel(); + bool IsCharging(); + double GetRemainingTime(); + + ~UPowerClient(); + + private: + UPowerClient(); + + enum States { + eState_Unknown = 0, + eState_Charging, + eState_Discharging, + eState_Empty, + eState_FullyCharged, + eState_PendingCharge, + eState_PendingDischarge + }; + + /** + * Update the currently tracked device. + */ + void UpdateTrackedDevices(); + + /** + * Update the battery info. + */ + bool GetBatteryInfo(); + + /** + * Watch battery device for status + */ + bool AddTrackedDevice(const char* devicePath); + + /** + * Callback used by 'DeviceChanged' signal. + */ + static void DeviceChanged(GDBusProxy* aProxy, gchar* aSenderName, + gchar* aSignalName, GVariant* aParameters, + UPowerClient* aListener); + + /** + * Callback used by 'PropertiesChanged' signal. + * This method is called when the the battery level changes. + * (Only with upower >= 0.99) + */ + static void DevicePropertiesChanged(GDBusProxy* aProxy, gchar* aSenderName, + gchar* aSignalName, GVariant* aParameters, + UPowerClient* aListener); + + RefPtr<GCancellable> mCancellable; + + // The DBus proxy object to upower. + RefPtr<GDBusProxy> mUPowerProxy; + + // The path of the tracked device. + GUniquePtr<gchar> mTrackedDevice; + + // The DBusGProxy for the tracked device. + RefPtr<GDBusProxy> mTrackedDeviceProxy; + + double mLevel; + bool mCharging; + double mRemainingTime; + + static UPowerClient* sInstance; + + static const guint sDeviceTypeBattery = 2; + static const guint64 kUPowerUnknownRemainingTime = 0; +}; + +/* + * Implementation of mozilla::hal_impl::EnableBatteryNotifications, + * mozilla::hal_impl::DisableBatteryNotifications, + * and mozilla::hal_impl::GetCurrentBatteryInformation. + */ + +void EnableBatteryNotifications() { + UPowerClient::GetInstance()->BeginListening(); +} + +void DisableBatteryNotifications() { + UPowerClient::GetInstance()->StopListening(); +} + +void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) { + UPowerClient* upowerClient = UPowerClient::GetInstance(); + + aBatteryInfo->level() = upowerClient->GetLevel(); + aBatteryInfo->charging() = upowerClient->IsCharging(); + aBatteryInfo->remainingTime() = upowerClient->GetRemainingTime(); +} + +/* + * Following is the implementation of UPowerClient. + */ + +UPowerClient* UPowerClient::sInstance = nullptr; + +/* static */ +UPowerClient* UPowerClient::GetInstance() { + if (!sInstance) { + sInstance = new UPowerClient(); + } + + return sInstance; +} + +UPowerClient::UPowerClient() + : mLevel(kDefaultLevel), + mCharging(kDefaultCharging), + mRemainingTime(kDefaultRemainingTime) {} + +UPowerClient::~UPowerClient() { + NS_ASSERTION( + !mUPowerProxy && !mTrackedDevice && !mTrackedDeviceProxy && !mCancellable, + "The observers have not been correctly removed! " + "(StopListening should have been called)"); +} + +void UPowerClient::BeginListening() { + GUniquePtr<GError> error; + + mCancellable = dont_AddRef(g_cancellable_new()); + CreateDBusProxyForBus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, + /* aInterfaceInfo = */ nullptr, + "org.freedesktop.UPower", "/org/freedesktop/UPower", + "org.freedesktop.UPower", mCancellable) + ->Then( + GetCurrentSerialEventTarget(), __func__, + // It's safe to capture this as we use mCancellable to stop + // listening. + [this](RefPtr<GDBusProxy>&& aProxy) { + mUPowerProxy = std::move(aProxy); + UpdateTrackedDevices(); + }, + [](GUniquePtr<GError>&& aError) { + if (!g_error_matches(aError.get(), G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning( + "Failed to create DBus proxy for org.freedesktop.UPower: " + "%s\n", + aError->message); + } + }); +} + +void UPowerClient::StopListening() { + if (mUPowerProxy) { + g_signal_handlers_disconnect_by_func(mUPowerProxy, (void*)DeviceChanged, + this); + } + if (mCancellable) { + g_cancellable_cancel(mCancellable); + mCancellable = nullptr; + } + + mTrackedDeviceProxy = nullptr; + mTrackedDevice = nullptr; + mUPowerProxy = nullptr; + + // We should now show the default values, not the latest we got. + mLevel = kDefaultLevel; + mCharging = kDefaultCharging; + mRemainingTime = kDefaultRemainingTime; +} + +bool UPowerClient::AddTrackedDevice(const char* aDevicePath) { + RefPtr<GDBusProxy> proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, + "org.freedesktop.UPower", aDevicePath, "org.freedesktop.UPower.Device", + mCancellable, nullptr)); + if (!proxy) { + return false; + } + + RefPtr<GVariant> deviceType = + dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "Type")); + if (NS_WARN_IF(!deviceType || + !g_variant_is_of_type(deviceType, G_VARIANT_TYPE_UINT32))) { + return false; + } + + if (g_variant_get_uint32(deviceType) != sDeviceTypeBattery) { + return false; + } + + GUniquePtr<gchar> device(g_strdup(aDevicePath)); + mTrackedDevice = std::move(device); + mTrackedDeviceProxy = std::move(proxy); + + if (!GetBatteryInfo()) { + return false; + } + hal::NotifyBatteryChange( + hal::BatteryInformation(mLevel, mCharging, mRemainingTime)); + + g_signal_connect(mTrackedDeviceProxy, "g-signal", + G_CALLBACK(DevicePropertiesChanged), this); + return true; +} + +void UPowerClient::UpdateTrackedDevices() { + // Reset the current tracked device: + g_signal_handlers_disconnect_by_func(mUPowerProxy, (void*)DeviceChanged, + this); + + mTrackedDevice = nullptr; + mTrackedDeviceProxy = nullptr; + + DBusProxyCall(mUPowerProxy, "EnumerateDevices", nullptr, + G_DBUS_CALL_FLAGS_NONE, -1, mCancellable) + ->Then( + GetCurrentSerialEventTarget(), __func__, + // It's safe to capture this as we use mCancellable to stop + // listening. + [this](RefPtr<GVariant>&& aResult) { + RefPtr<GVariant> variant = + dont_AddRef(g_variant_get_child_value(aResult.get(), 0)); + if (!variant || !g_variant_is_of_type( + variant, G_VARIANT_TYPE_OBJECT_PATH_ARRAY)) { + g_warning( + "Failed to enumerate devices of org.freedesktop.UPower: " + "wrong param %s\n", + g_variant_get_type_string(aResult.get())); + return; + } + gsize num = g_variant_n_children(variant); + for (gsize i = 0; i < num; i++) { + const char* devicePath = g_variant_get_string( + g_variant_get_child_value(variant, i), nullptr); + if (!devicePath) { + g_warning( + "Failed to enumerate devices of org.freedesktop.UPower: " + "missing device?\n"); + return; + } + /* + * We are looking for the first device that is a battery. + * TODO: we could try to combine more than one battery. + */ + if (AddTrackedDevice(devicePath)) { + break; + } + } + g_signal_connect(mUPowerProxy, "g-signal", + G_CALLBACK(DeviceChanged), this); + }, + [this](GUniquePtr<GError>&& aError) { + if (!g_error_matches(aError.get(), G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + g_warning( + "Failed to enumerate devices of org.freedesktop.UPower: %s\n", + aError->message); + } + g_signal_connect(mUPowerProxy, "g-signal", + G_CALLBACK(DeviceChanged), this); + }); +} + +/* static */ +void UPowerClient::DeviceChanged(GDBusProxy* aProxy, gchar* aSenderName, + gchar* aSignalName, GVariant* aParameters, + UPowerClient* aListener) { + // Added new device. Act only if we're missing any tracked device + if (!g_strcmp0(aSignalName, "DeviceAdded")) { + if (aListener->mTrackedDevice) { + return; + } + } else if (!g_strcmp0(aSignalName, "DeviceRemoved")) { + if (g_strcmp0(aSenderName, aListener->mTrackedDevice.get())) { + return; + } + } + aListener->UpdateTrackedDevices(); +} + +/* static */ +void UPowerClient::DevicePropertiesChanged(GDBusProxy* aProxy, + gchar* aSenderName, + gchar* aSignalName, + GVariant* aParameters, + UPowerClient* aListener) { + if (aListener->GetBatteryInfo()) { + hal::NotifyBatteryChange(hal::BatteryInformation( + sInstance->mLevel, sInstance->mCharging, sInstance->mRemainingTime)); + } +} + +bool UPowerClient::GetBatteryInfo() { + bool isFull = false; + + /* + * State values are confusing... + * First of all, after looking at upower sources (0.9.13), it seems that + * PendingDischarge and PendingCharge are not used. + * In addition, FullyCharged and Empty states are not clear because we do not + * know if the battery is actually charging or not. Those values come directly + * from sysfs (in the Linux kernel) which have four states: "Empty", "Full", + * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only + * related to the level, not to the charging state. + * In this code, we are going to assume that Full means charging and Empty + * means discharging because if that is not the case, the state should not + * last a long time (actually, it should disappear at the following update). + * It might be even very hard to see real cases where the state is Empty and + * the battery is charging or the state is Full and the battery is discharging + * given that plugging/unplugging the battery should have an impact on the + * level. + */ + + if (!mTrackedDeviceProxy) { + return false; + } + + RefPtr<GVariant> value = dont_AddRef( + g_dbus_proxy_get_cached_property(mTrackedDeviceProxy, "State")); + if (NS_WARN_IF(!value || + !g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32))) { + return false; + } + + switch (g_variant_get_uint32(value)) { + case eState_Unknown: + mCharging = kDefaultCharging; + break; + case eState_FullyCharged: + isFull = true; + [[fallthrough]]; + case eState_Charging: + case eState_PendingCharge: + mCharging = true; + break; + case eState_Discharging: + case eState_Empty: + case eState_PendingDischarge: + mCharging = false; + break; + } + + /* + * The battery level might be very close to 100% (like 99%) without + * increasing. It seems that upower sets the battery state as 'full' in that + * case so we should trust it and not even try to get the value. + */ + if (isFull) { + mLevel = 1.0; + } else { + value = dont_AddRef( + g_dbus_proxy_get_cached_property(mTrackedDeviceProxy, "Percentage")); + if (NS_WARN_IF(!value || + !g_variant_is_of_type(value, G_VARIANT_TYPE_DOUBLE))) { + return false; + } + mLevel = round(g_variant_get_double(value)) * 0.01; + } + + if (isFull) { + mRemainingTime = 0; + } else { + value = dont_AddRef(g_dbus_proxy_get_cached_property( + mTrackedDeviceProxy, mCharging ? "TimeToFull" : "TimeToEmpty")); + if (NS_WARN_IF(!value || + !g_variant_is_of_type(value, G_VARIANT_TYPE_INT64))) { + return false; + } + mRemainingTime = g_variant_get_int64(value); + if (mRemainingTime == kUPowerUnknownRemainingTime) { + mRemainingTime = kUnknownRemainingTime; + } + } + return true; +} + +double UPowerClient::GetLevel() { return mLevel; } + +bool UPowerClient::IsCharging() { return mCharging; } + +double UPowerClient::GetRemainingTime() { return mRemainingTime; } + +} // namespace mozilla::hal_impl diff --git a/hal/moz.build b/hal/moz.build new file mode 100644 index 0000000000..912a157372 --- /dev/null +++ b/hal/moz.build @@ -0,0 +1,139 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Hardware Abstraction Layer (HAL)") + +EXPORTS.mozilla += [ + "Hal.h", + "HalBatteryInformation.h", + "HalImpl.h", + "HalIPCUtils.h", + "HalNetworkInformation.h", + "HalSandbox.h", + "HalScreenConfiguration.h", + "HalSensor.h", + "HalTypes.h", + "HalWakeLock.h", + "HalWakeLockInformation.h", +] + +UNIFIED_SOURCES += [ + "HalWakeLock.cpp", + "sandbox/SandboxHal.cpp", + "WindowIdentifier.cpp", +] + +# Hal.cpp cannot be built in unified mode because it relies on HalImpl.h. +SOURCES += [ + "Hal.cpp", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + LOCAL_INCLUDES += [ + "/widget/android", + ] + UNIFIED_SOURCES += [ + "android/AndroidHeterogeneousCpuInfo.cpp", + "android/AndroidPerformanceHintManager.cpp", + "android/AndroidProcessPriority.cpp", + "android/AndroidSensor.cpp", + ] + # AndroidHal.cpp cannot be built in unified mode because it relies on HalImpl.h. + SOURCES += [ + "android/AndroidHal.cpp", + ] +elif CONFIG["OS_TARGET"] == "Linux": + UNIFIED_SOURCES += [ + "fallback/FallbackHeterogeneousCpuInfo.cpp", + "fallback/FallbackScreenConfiguration.cpp", + "fallback/FallbackSensor.cpp", + "fallback/FallbackVibration.cpp", + "linux/LinuxProcessPriority.cpp", + ] + if CONFIG["MOZ_ENABLE_DBUS"]: + UNIFIED_SOURCES += [ + "linux/UPowerClient.cpp", + ] + else: + UNIFIED_SOURCES += [ + "fallback/FallbackBattery.cpp", + ] +elif CONFIG["OS_TARGET"] == "WINNT": + UNIFIED_SOURCES += [ + "fallback/FallbackHeterogeneousCpuInfo.cpp", + "fallback/FallbackVibration.cpp", + "windows/WindowsProcessPriority.cpp", + "windows/WindowsScreenConfiguration.cpp", + "windows/WindowsSensor.cpp", + ] + # WindowsBattery.cpp cannot be built in unified mode because it relies on HalImpl.h. + SOURCES += [ + "windows/WindowsBattery.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "cocoa/CocoaBattery.cpp", + "cocoa/CocoaHeterogeneousCpuInfo.cpp", + "fallback/FallbackProcessPriority.cpp", + "fallback/FallbackScreenConfiguration.cpp", + "fallback/FallbackVibration.cpp", + ] +elif CONFIG["OS_TARGET"] in ("OpenBSD", "NetBSD", "FreeBSD", "DragonFly"): + UNIFIED_SOURCES += [ + "fallback/FallbackHeterogeneousCpuInfo.cpp", + "fallback/FallbackProcessPriority.cpp", + "fallback/FallbackScreenConfiguration.cpp", + "fallback/FallbackSensor.cpp", + "fallback/FallbackVibration.cpp", + ] + if CONFIG["MOZ_ENABLE_DBUS"]: + UNIFIED_SOURCES += [ + "linux/UPowerClient.cpp", + ] + else: + UNIFIED_SOURCES += [ + "fallback/FallbackBattery.cpp", + ] +else: + UNIFIED_SOURCES += [ + "fallback/FallbackBattery.cpp", + "fallback/FallbackHeterogeneousCpuInfo.cpp", + "fallback/FallbackProcessPriority.cpp", + "fallback/FallbackScreenConfiguration.cpp", + "fallback/FallbackSensor.cpp", + "fallback/FallbackVibration.cpp", + ] + +# Fallbacks for backends implemented on Android only. +if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": + UNIFIED_SOURCES += [ + "fallback/FallbackNetwork.cpp", + "fallback/FallbackPerformanceHintManager.cpp", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "cocoa/CocoaSensor.mm", + "cocoa/smslib.mm", + ] + +IPDL_SOURCES = [ + "sandbox/PHal.ipdl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "/dom/base", +] + +CFLAGS += CONFIG["GLIB_CFLAGS"] +CFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] +CXXFLAGS += CONFIG["GLIB_CFLAGS"] +CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] diff --git a/hal/sandbox/PHal.ipdl b/hal/sandbox/PHal.ipdl new file mode 100644 index 0000000000..2b8ba990fb --- /dev/null +++ b/hal/sandbox/PHal.ipdl @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PContent; +include protocol PBrowser; + +include "mozilla/dom/ReferrerInfoUtils.h"; +include "mozilla/GfxMessageUtils.h"; + +using mozilla::hal::ScreenOrientation from "mozilla/HalIPCUtils.h"; +using mozilla::hal::SensorType from "mozilla/HalSensor.h"; +using mozilla::hal::WakeLockControl from "mozilla/HalTypes.h"; +using mozilla::hal::ProcessPriority from "mozilla/HalTypes.h"; +using nsIntRect from "nsRect.h"; +using PRTime from "prtime.h"; + +namespace mozilla { + +namespace hal { +struct BatteryInformation { + double level; + bool charging; + double remainingTime; +}; + +struct SensorData { + SensorType sensor; + PRTime timestamp; + float[] values; +}; + +struct NetworkInformation { + uint32_t type; + bool isWifi; + uint32_t dhcpGateway; +}; + +struct WakeLockInformation { + nsString topic; + uint32_t numLocks; + uint32_t numHidden; + uint64_t[] lockingProcesses; +}; + +} // namespace hal + +namespace hal_sandbox { + +[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual] +sync protocol PHal { + manager PContent; + +child: + async NotifyBatteryChange(BatteryInformation aBatteryInfo); + async NotifyNetworkChange(NetworkInformation aNetworkInfo); + async NotifyWakeLockChange(WakeLockInformation aWakeLockInfo); + +parent: + async Vibrate(uint32_t[] pattern, uint64_t[] id, PBrowser browser); + async CancelVibrate(uint64_t[] id, PBrowser browser); + + async EnableBatteryNotifications(); + async DisableBatteryNotifications(); + sync GetCurrentBatteryInformation() + returns (BatteryInformation aBatteryInfo); + + async EnableNetworkNotifications(); + async DisableNetworkNotifications(); + sync GetCurrentNetworkInformation() + returns (NetworkInformation aNetworkInfo); + + async ModifyWakeLock(nsString aTopic, + WakeLockControl aLockAdjust, + WakeLockControl aHiddenAdjust); + async EnableWakeLockNotifications(); + async DisableWakeLockNotifications(); + sync GetWakeLockInfo(nsString aTopic) + returns (WakeLockInformation aWakeLockInfo); + + async LockScreenOrientation(ScreenOrientation aOrientation) + returns (nsresult result); + async UnlockScreenOrientation(); + +child: + async NotifySensorChange(SensorData aSensorData); + +parent: + async EnableSensorNotifications(SensorType aSensor); + async DisableSensorNotifications(SensorType aSensor); + + async __delete__(); +}; + +} // namespace hal +} // namespace mozilla diff --git a/hal/sandbox/SandboxHal.cpp b/hal/sandbox/SandboxHal.cpp new file mode 100644 index 0000000000..4a6bdbdd20 --- /dev/null +++ b/hal/sandbox/SandboxHal.cpp @@ -0,0 +1,340 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalLog.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/hal_sandbox/PHalChild.h" +#include "mozilla/hal_sandbox/PHalParent.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/EnumeratedRange.h" +#include "mozilla/HalWakeLock.h" +#include "mozilla/Observer.h" +#include "mozilla/Unused.h" +#include "WindowIdentifier.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::hal; + +namespace mozilla { +namespace hal_sandbox { + +static bool sHalChildDestroyed = false; + +bool HalChildDestroyed() { return sHalChildDestroyed; } + +static PHalChild* sHal; +static PHalChild* Hal() { + if (!sHal) { + sHal = ContentChild::GetSingleton()->SendPHalConstructor(); + } + return sHal; +} + +void Vibrate(const nsTArray<uint32_t>& pattern, WindowIdentifier&& id) { + HAL_LOG("Vibrate: Sending to parent process."); + + WindowIdentifier newID(std::move(id)); + newID.AppendProcessID(); + if (BrowserChild* bc = BrowserChild::GetFrom(newID.GetWindow())) { + Hal()->SendVibrate(pattern, newID.AsArray(), WrapNotNull(bc)); + } +} + +void CancelVibrate(WindowIdentifier&& id) { + HAL_LOG("CancelVibrate: Sending to parent process."); + + WindowIdentifier newID(std::move(id)); + newID.AppendProcessID(); + if (BrowserChild* bc = BrowserChild::GetFrom(newID.GetWindow())) { + Hal()->SendCancelVibrate(newID.AsArray(), WrapNotNull(bc)); + } +} + +void EnableBatteryNotifications() { Hal()->SendEnableBatteryNotifications(); } + +void DisableBatteryNotifications() { Hal()->SendDisableBatteryNotifications(); } + +void GetCurrentBatteryInformation(BatteryInformation* aBatteryInfo) { + Hal()->SendGetCurrentBatteryInformation(aBatteryInfo); +} + +void EnableNetworkNotifications() { Hal()->SendEnableNetworkNotifications(); } + +void DisableNetworkNotifications() { Hal()->SendDisableNetworkNotifications(); } + +void GetCurrentNetworkInformation(NetworkInformation* aNetworkInfo) { + Hal()->SendGetCurrentNetworkInformation(aNetworkInfo); +} + +RefPtr<GenericNonExclusivePromise> LockScreenOrientation( + const hal::ScreenOrientation& aOrientation) { + return Hal() + ->SendLockScreenOrientation(aOrientation) + ->Then(GetCurrentSerialEventTarget(), __func__, + [](const mozilla::MozPromise<nsresult, ipc::ResponseRejectReason, + true>::ResolveOrRejectValue& aValue) { + if (aValue.IsResolve()) { + if (NS_SUCCEEDED(aValue.ResolveValue())) { + return GenericNonExclusivePromise::CreateAndResolve( + true, __func__); + } + return GenericNonExclusivePromise::CreateAndReject( + aValue.ResolveValue(), __func__); + } + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + }); +} + +void UnlockScreenOrientation() { Hal()->SendUnlockScreenOrientation(); } + +void EnableSensorNotifications(SensorType aSensor) { + Hal()->SendEnableSensorNotifications(aSensor); +} + +void DisableSensorNotifications(SensorType aSensor) { + Hal()->SendDisableSensorNotifications(aSensor); +} + +void EnableWakeLockNotifications() { Hal()->SendEnableWakeLockNotifications(); } + +void DisableWakeLockNotifications() { + Hal()->SendDisableWakeLockNotifications(); +} + +void ModifyWakeLock(const nsAString& aTopic, WakeLockControl aLockAdjust, + WakeLockControl aHiddenAdjust) { + Hal()->SendModifyWakeLock(aTopic, aLockAdjust, aHiddenAdjust); +} + +void GetWakeLockInfo(const nsAString& aTopic, + WakeLockInformation* aWakeLockInfo) { + Hal()->SendGetWakeLockInfo(aTopic, aWakeLockInfo); +} + +bool EnableAlarm() { + MOZ_CRASH("Alarms can't be programmed from sandboxed contexts. Yet."); +} + +void DisableAlarm() { + MOZ_CRASH("Alarms can't be programmed from sandboxed contexts. Yet."); +} + +bool SetAlarm(int32_t aSeconds, int32_t aNanoseconds) { + MOZ_CRASH("Alarms can't be programmed from sandboxed contexts. Yet."); +} + +void SetProcessPriority(int aPid, ProcessPriority aPriority) { + MOZ_CRASH("Only the main process may set processes' priorities."); +} + +class HalParent : public PHalParent, + public BatteryObserver, + public NetworkObserver, + public ISensorObserver, + public WakeLockObserver { + public: + virtual void ActorDestroy(ActorDestroyReason aWhy) override { + // NB: you *must* unconditionally unregister your observer here, + // if it *may* be registered below. + hal::UnregisterBatteryObserver(this); + hal::UnregisterNetworkObserver(this); + for (auto sensor : MakeEnumeratedRange(NUM_SENSOR_TYPE)) { + hal::UnregisterSensorObserver(sensor, this); + } + hal::UnregisterWakeLockObserver(this); + } + + virtual mozilla::ipc::IPCResult RecvVibrate( + nsTArray<unsigned int>&& pattern, nsTArray<uint64_t>&& id, + NotNull<PBrowserParent*> browserParent) override { + // We give all content vibration permission. + // BrowserParent *browserParent = BrowserParent::GetFrom(browserParent); + /* xxxkhuey wtf + nsCOMPtr<nsIDOMWindow> window = + do_QueryInterface(browserParent->GetBrowserDOMWindow()); + */ + hal::Vibrate(pattern, WindowIdentifier(std::move(id), nullptr)); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvCancelVibrate( + nsTArray<uint64_t>&& id, + NotNull<PBrowserParent*> browserParent) override { + // BrowserParent *browserParent = BrowserParent::GetFrom(browserParent); + /* XXXkhuey wtf + nsCOMPtr<nsIDOMWindow> window = + browserParent->GetBrowserDOMWindow(); + */ + hal::CancelVibrate(WindowIdentifier(std::move(id), nullptr)); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvEnableBatteryNotifications() override { + // We give all content battery-status permission. + hal::RegisterBatteryObserver(this); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvDisableBatteryNotifications() override { + hal::UnregisterBatteryObserver(this); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvGetCurrentBatteryInformation( + BatteryInformation* aBatteryInfo) override { + // We give all content battery-status permission. + hal::GetCurrentBatteryInformation(aBatteryInfo); + return IPC_OK(); + } + + void Notify(const BatteryInformation& aBatteryInfo) override { + Unused << SendNotifyBatteryChange(aBatteryInfo); + } + + virtual mozilla::ipc::IPCResult RecvEnableNetworkNotifications() override { + // We give all content access to this network-status information. + hal::RegisterNetworkObserver(this); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvDisableNetworkNotifications() override { + hal::UnregisterNetworkObserver(this); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvGetCurrentNetworkInformation( + NetworkInformation* aNetworkInfo) override { + hal::GetCurrentNetworkInformation(aNetworkInfo); + return IPC_OK(); + } + + void Notify(const NetworkInformation& aNetworkInfo) override { + Unused << SendNotifyNetworkChange(aNetworkInfo); + } + + virtual mozilla::ipc::IPCResult RecvLockScreenOrientation( + const ScreenOrientation& aOrientation, + LockScreenOrientationResolver&& aResolve) override { + // FIXME/bug 777980: unprivileged content may only lock + // orientation while fullscreen. We should check whether the + // request comes from an actor in a process that might be + // fullscreen. We don't have that information currently. + + hal::LockScreenOrientation(aOrientation) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolve](const GenericNonExclusivePromise::ResolveOrRejectValue& + aValue) { + if (aValue.IsResolve()) { + MOZ_ASSERT(aValue.ResolveValue()); + aResolve(NS_OK); + return; + } + aResolve(aValue.RejectValue()); + }); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvUnlockScreenOrientation() override { + hal::UnlockScreenOrientation(); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvEnableSensorNotifications( + const SensorType& aSensor) override { + // We currently allow any content to register device-sensor + // listeners. + hal::RegisterSensorObserver(aSensor, this); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvDisableSensorNotifications( + const SensorType& aSensor) override { + hal::UnregisterSensorObserver(aSensor, this); + return IPC_OK(); + } + + void Notify(const SensorData& aSensorData) override { + Unused << SendNotifySensorChange(aSensorData); + } + + virtual mozilla::ipc::IPCResult RecvModifyWakeLock( + const nsAString& aTopic, const WakeLockControl& aLockAdjust, + const WakeLockControl& aHiddenAdjust) override { + // We allow arbitrary content to use wake locks. + uint64_t id = static_cast<ContentParent*>(Manager())->ChildID(); + hal_impl::ModifyWakeLockWithChildID(aTopic, aLockAdjust, aHiddenAdjust, id); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvEnableWakeLockNotifications() override { + // We allow arbitrary content to use wake locks. + hal::RegisterWakeLockObserver(this); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvDisableWakeLockNotifications() override { + hal::UnregisterWakeLockObserver(this); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvGetWakeLockInfo( + const nsAString& aTopic, WakeLockInformation* aWakeLockInfo) override { + hal::GetWakeLockInfo(aTopic, aWakeLockInfo); + return IPC_OK(); + } + + void Notify(const WakeLockInformation& aWakeLockInfo) override { + Unused << SendNotifyWakeLockChange(aWakeLockInfo); + } +}; + +class HalChild : public PHalChild { + public: + virtual void ActorDestroy(ActorDestroyReason aWhy) override { + sHalChildDestroyed = true; + } + + virtual mozilla::ipc::IPCResult RecvNotifyBatteryChange( + const BatteryInformation& aBatteryInfo) override { + hal::NotifyBatteryChange(aBatteryInfo); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvNotifySensorChange( + const hal::SensorData& aSensorData) override; + + virtual mozilla::ipc::IPCResult RecvNotifyNetworkChange( + const NetworkInformation& aNetworkInfo) override { + hal::NotifyNetworkChange(aNetworkInfo); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvNotifyWakeLockChange( + const WakeLockInformation& aWakeLockInfo) override { + hal::NotifyWakeLockChange(aWakeLockInfo); + return IPC_OK(); + } +}; + +mozilla::ipc::IPCResult HalChild::RecvNotifySensorChange( + const hal::SensorData& aSensorData) { + hal::NotifySensorChange(aSensorData); + + return IPC_OK(); +} + +PHalChild* CreateHalChild() { return new HalChild(); } + +PHalParent* CreateHalParent() { return new HalParent(); } + +} // namespace hal_sandbox +} // namespace mozilla diff --git a/hal/sandbox/SandboxHal.h b/hal/sandbox/SandboxHal.h new file mode 100644 index 0000000000..b735e4da78 --- /dev/null +++ b/hal/sandbox/SandboxHal.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_SandboxHal_h +#define mozilla_SandboxHal_h + +namespace mozilla { +namespace hal_sandbox { + +class PHalChild; +class PHalParent; + +PHalChild* CreateHalChild(); + +PHalParent* CreateHalParent(); + +} // namespace hal_sandbox +} // namespace mozilla + +#endif // mozilla_SandboxHal_h diff --git a/hal/windows/WindowsBattery.cpp b/hal/windows/WindowsBattery.cpp new file mode 100644 index 0000000000..fba786c8a2 --- /dev/null +++ b/hal/windows/WindowsBattery.cpp @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalImpl.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/battery/Constants.h" + +#include <windows.h> + +using namespace mozilla::dom::battery; + +namespace mozilla { +namespace hal_impl { + +static HPOWERNOTIFY sPowerHandle = nullptr; +static HPOWERNOTIFY sCapacityHandle = nullptr; +static HWND sHWnd = nullptr; + +static LRESULT CALLBACK BatteryWindowProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam) { + if (msg != WM_POWERBROADCAST || wParam != PBT_POWERSETTINGCHANGE) { + return DefWindowProc(hwnd, msg, wParam, lParam); + } + + hal::BatteryInformation currentInfo; + + // Since we need update remainingTime, we cannot use LPARAM. + hal_impl::GetCurrentBatteryInformation(¤tInfo); + + hal::NotifyBatteryChange(currentInfo); + return TRUE; +} + +void EnableBatteryNotifications() { + // Create custom window to watch battery event + // If we can get Gecko's window handle, this is unnecessary. + + if (sHWnd == nullptr) { + WNDCLASSW wc; + HMODULE hSelf = GetModuleHandle(nullptr); + + if (!GetClassInfoW(hSelf, L"MozillaBatteryClass", &wc)) { + ZeroMemory(&wc, sizeof(WNDCLASSW)); + wc.hInstance = hSelf; + wc.lpfnWndProc = BatteryWindowProc; + wc.lpszClassName = L"MozillaBatteryClass"; + RegisterClassW(&wc); + } + + sHWnd = CreateWindowW(L"MozillaBatteryClass", L"Battery Watcher", 0, 0, 0, + 0, 0, nullptr, nullptr, hSelf, nullptr); + } + + if (sHWnd == nullptr) { + return; + } + + sPowerHandle = RegisterPowerSettingNotification( + sHWnd, &GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE); + sCapacityHandle = RegisterPowerSettingNotification( + sHWnd, &GUID_BATTERY_PERCENTAGE_REMAINING, DEVICE_NOTIFY_WINDOW_HANDLE); +} + +void DisableBatteryNotifications() { + if (sPowerHandle) { + UnregisterPowerSettingNotification(sPowerHandle); + sPowerHandle = nullptr; + } + + if (sCapacityHandle) { + UnregisterPowerSettingNotification(sCapacityHandle); + sCapacityHandle = nullptr; + } + + if (sHWnd) { + DestroyWindow(sHWnd); + sHWnd = nullptr; + } +} + +void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) { + SYSTEM_POWER_STATUS status; + if (!GetSystemPowerStatus(&status)) { + aBatteryInfo->level() = kDefaultLevel; + aBatteryInfo->charging() = kDefaultCharging; + aBatteryInfo->remainingTime() = kDefaultRemainingTime; + return; + } + + aBatteryInfo->level() = status.BatteryLifePercent == 255 + ? kDefaultLevel + : ((double)status.BatteryLifePercent) / 100.0; + aBatteryInfo->charging() = (status.ACLineStatus != 0); + if (status.ACLineStatus != 0) { + if (aBatteryInfo->level() == 1.0) { + // GetSystemPowerStatus API may returns -1 for BatteryFullLifeTime. + // So, if battery is 100%, set kDefaultRemainingTime at force. + aBatteryInfo->remainingTime() = kDefaultRemainingTime; + } else { + aBatteryInfo->remainingTime() = status.BatteryFullLifeTime == (DWORD)-1 + ? kUnknownRemainingTime + : status.BatteryFullLifeTime; + } + } else { + aBatteryInfo->remainingTime() = status.BatteryLifeTime == (DWORD)-1 + ? kUnknownRemainingTime + : status.BatteryLifeTime; + } +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/windows/WindowsProcessPriority.cpp b/hal/windows/WindowsProcessPriority.cpp new file mode 100644 index 0000000000..fe9b78133b --- /dev/null +++ b/hal/windows/WindowsProcessPriority.cpp @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "HalLog.h" +#include "nsWindowsHelpers.h" // for nsAutoHandle and nsModuleHandle +#include "mozilla/StaticPrefs_dom.h" + +#include <windows.h> + +using namespace mozilla::hal; + +namespace mozilla { +namespace hal_impl { + +void SetProcessPriority(int aPid, ProcessPriority aPriority) { + HAL_LOG("WindowsProcessPriority - SetProcessPriority(%d, %s)\n", aPid, + ProcessPriorityToString(aPriority)); + + nsAutoHandle processHandle( + ::OpenProcess(PROCESS_SET_INFORMATION, FALSE, aPid)); + if (processHandle) { + DWORD priority = NORMAL_PRIORITY_CLASS; + if (aPriority == PROCESS_PRIORITY_BACKGROUND) { + priority = IDLE_PRIORITY_CLASS; + } else if (aPriority == PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE) { + priority = BELOW_NORMAL_PRIORITY_CLASS; + } + + if (::SetPriorityClass(processHandle, priority)) { + HAL_LOG("WindowsProcessPriority - priority set to %d for pid %d\n", + aPriority, aPid); + } + + // Set the process into or out of EcoQoS. + static bool alreadyInitialized = false; + static decltype(::SetProcessInformation)* setProcessInformation = nullptr; + if (!alreadyInitialized) { + if (aPriority == PROCESS_PRIORITY_PARENT_PROCESS || + !StaticPrefs::dom_ipc_processPriorityManager_backgroundUsesEcoQoS()) { + return; + } + + alreadyInitialized = true; + // SetProcessInformation only exists on Windows 8 and later. + nsModuleHandle module(LoadLibrary(L"Kernel32.dll")); + if (module) { + setProcessInformation = + (decltype(::SetProcessInformation)*)GetProcAddress( + module, "SetProcessInformation"); + } + } + if (!setProcessInformation) { + return; + } + + PROCESS_POWER_THROTTLING_STATE PowerThrottling; + RtlZeroMemory(&PowerThrottling, sizeof(PowerThrottling)); + PowerThrottling.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION; + PowerThrottling.ControlMask = PROCESS_POWER_THROTTLING_EXECUTION_SPEED; + PowerThrottling.StateMask = + (aPriority == PROCESS_PRIORITY_BACKGROUND) && + StaticPrefs:: + dom_ipc_processPriorityManager_backgroundUsesEcoQoS() + ? PROCESS_POWER_THROTTLING_EXECUTION_SPEED + : 0; + if (setProcessInformation(processHandle, ProcessPowerThrottling, + &PowerThrottling, sizeof(PowerThrottling))) { + HAL_LOG("SetProcessInformation(%d, %s)\n", aPid, + aPriority == PROCESS_PRIORITY_BACKGROUND ? "eco" : "normal"); + } else { + HAL_LOG("SetProcessInformation failed for %d\n", aPid); + } + } +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/windows/WindowsScreenConfiguration.cpp b/hal/windows/WindowsScreenConfiguration.cpp new file mode 100644 index 0000000000..d7f17d73bf --- /dev/null +++ b/hal/windows/WindowsScreenConfiguration.cpp @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" +#include "mozilla/widget/ScreenManager.h" +#include "nsIWindowsUIUtils.h" +#include "WinUtils.h" + +#include <windows.h> + +namespace mozilla { +namespace hal_impl { + +static decltype(SetDisplayAutoRotationPreferences)* + sSetDisplayAutoRotationPreferences = nullptr; + +RefPtr<GenericNonExclusivePromise> LockScreenOrientation( + const hal::ScreenOrientation& aOrientation) { + AR_STATE state; + if (!widget::WinUtils::GetAutoRotationState(&state)) { + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_DOM_NOT_SUPPORTED_ERR, __func__); + } + + if (state & (AR_DISABLED | AR_REMOTESESSION | AR_MULTIMON | AR_NOSENSOR | + AR_NOT_SUPPORTED | AR_LAPTOP | AR_DOCKED)) { + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_DOM_NOT_SUPPORTED_ERR, __func__); + } + + if (!sSetDisplayAutoRotationPreferences) { + HMODULE user32dll = GetModuleHandleW(L"user32.dll"); + if (user32dll) { + sSetDisplayAutoRotationPreferences = + (decltype(SetDisplayAutoRotationPreferences)*)GetProcAddress( + user32dll, "SetDisplayAutoRotationPreferences"); + } + if (!sSetDisplayAutoRotationPreferences) { + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_DOM_NOT_SUPPORTED_ERR, __func__); + } + } + + ORIENTATION_PREFERENCE orientation = ORIENTATION_PREFERENCE_NONE; + + if (aOrientation == hal::ScreenOrientation::Default) { + // Actually, current screen is single and tablet mode according to + // GetAutoRotationState. So get primary screen data for natural orientation. + RefPtr<widget::Screen> screen = + widget::ScreenManager::GetSingleton().GetPrimaryScreen(); + hal::ScreenOrientation defaultOrientation = + screen->GetDefaultOrientationType(); + if (defaultOrientation == hal::ScreenOrientation::LandscapePrimary) { + orientation = ORIENTATION_PREFERENCE_LANDSCAPE; + } else { + orientation = ORIENTATION_PREFERENCE_PORTRAIT; + } + } else { + if (aOrientation & hal::ScreenOrientation::LandscapePrimary) { + orientation |= ORIENTATION_PREFERENCE_LANDSCAPE; + } + if (aOrientation & hal::ScreenOrientation::LandscapeSecondary) { + orientation |= ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED; + } + if (aOrientation & hal::ScreenOrientation::PortraitPrimary) { + orientation |= ORIENTATION_PREFERENCE_PORTRAIT; + } + if (aOrientation & hal::ScreenOrientation::PortraitSecondary) { + orientation |= ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED; + } + } + + if (!sSetDisplayAutoRotationPreferences(orientation)) { + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR, + __func__); + } + + return GenericNonExclusivePromise::CreateAndResolve(true, __func__); +} + +void UnlockScreenOrientation() { + if (!sSetDisplayAutoRotationPreferences) { + return; + } + // This does nothing if the device doesn't support orientation lock + sSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/windows/WindowsSensor.cpp b/hal/windows/WindowsSensor.cpp new file mode 100644 index 0000000000..73bd8bc57c --- /dev/null +++ b/hal/windows/WindowsSensor.cpp @@ -0,0 +1,170 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Hal.h" + +#include <sensorsapi.h> +#include <sensors.h> +#include <portabledevicetypes.h> + +#define MEAN_GRAVITY 9.80665 +#define DEFAULT_SENSOR_POLL 100 + +using namespace mozilla::hal; + +namespace mozilla { +namespace hal_impl { + +static RefPtr<ISensor> sAccelerometer; + +class SensorEvent final : public ISensorEvents { + public: + SensorEvent() : mCount(0) {} + + // IUnknown interface + + STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&mCount); } + + STDMETHODIMP_(ULONG) Release() { + ULONG count = InterlockedDecrement(&mCount); + if (!count) { + delete this; + return 0; + } + return count; + } + + STDMETHODIMP QueryInterface(REFIID iid, void** ppv) { + if (iid == IID_IUnknown) { + *ppv = static_cast<IUnknown*>(this); + } else if (iid == IID_ISensorEvents) { + *ppv = static_cast<ISensorEvents*>(this); + } else { + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + // ISensorEvents interface + + STDMETHODIMP OnEvent(ISensor* aSensor, REFGUID aId, + IPortableDeviceValues* aData) { + return S_OK; + } + + STDMETHODIMP OnLeave(REFSENSOR_ID aId) { return S_OK; } + + STDMETHODIMP OnStateChanged(ISensor* aSensor, SensorState state) { + return S_OK; + } + + STDMETHODIMP OnDataUpdated(ISensor* aSensor, ISensorDataReport* aReport) { + PROPVARIANT v; + HRESULT hr; + nsTArray<float> values; + + // X-axis acceleration in g's + hr = aReport->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_X_G, &v); + if (FAILED(hr)) { + return hr; + } + values.AppendElement(float(-v.dblVal * MEAN_GRAVITY)); + + // Y-axis acceleration in g's + hr = aReport->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_Y_G, &v); + if (FAILED(hr)) { + return hr; + } + values.AppendElement(float(-v.dblVal * MEAN_GRAVITY)); + + // Z-axis acceleration in g's + hr = aReport->GetSensorValue(SENSOR_DATA_TYPE_ACCELERATION_Z_G, &v); + if (FAILED(hr)) { + return hr; + } + values.AppendElement(float(-v.dblVal * MEAN_GRAVITY)); + + hal::SensorData sdata(hal::SENSOR_ACCELERATION, PR_Now(), values); + hal::NotifySensorChange(sdata); + + return S_OK; + } + + private: + ULONG mCount; +}; + +void EnableSensorNotifications(SensorType aSensor) { + if (aSensor != SENSOR_ACCELERATION) { + return; + } + + if (sAccelerometer) { + return; + } + + RefPtr<ISensorManager> manager; + if (FAILED(CoCreateInstance(CLSID_SensorManager, nullptr, + CLSCTX_INPROC_SERVER, IID_ISensorManager, + getter_AddRefs(manager)))) { + return; + } + + // accelerometer event + + RefPtr<ISensorCollection> collection; + if (FAILED(manager->GetSensorsByType(SENSOR_TYPE_ACCELEROMETER_3D, + getter_AddRefs(collection)))) { + return; + } + + ULONG count = 0; + collection->GetCount(&count); + if (!count) { + return; + } + + RefPtr<ISensor> sensor; + collection->GetAt(0, getter_AddRefs(sensor)); + if (!sensor) { + return; + } + + // Set report interval to 100ms if possible. + // Default value depends on drivers. + RefPtr<IPortableDeviceValues> values; + if (SUCCEEDED(CoCreateInstance( + CLSID_PortableDeviceValues, nullptr, CLSCTX_INPROC_SERVER, + IID_IPortableDeviceValues, getter_AddRefs(values)))) { + if (SUCCEEDED(values->SetUnsignedIntegerValue( + SENSOR_PROPERTY_CURRENT_REPORT_INTERVAL, DEFAULT_SENSOR_POLL))) { + RefPtr<IPortableDeviceValues> returns; + sensor->SetProperties(values, getter_AddRefs(returns)); + } + } + + RefPtr<SensorEvent> event = new SensorEvent(); + RefPtr<ISensorEvents> sensorEvents; + if (FAILED(event->QueryInterface(IID_ISensorEvents, + getter_AddRefs(sensorEvents)))) { + return; + } + + if (FAILED(sensor->SetEventSink(sensorEvents))) { + return; + } + + sAccelerometer = sensor; +} + +void DisableSensorNotifications(SensorType aSensor) { + if (aSensor == SENSOR_ACCELERATION && sAccelerometer) { + sAccelerometer->SetEventSink(nullptr); + sAccelerometer = nullptr; + } +} + +} // namespace hal_impl +} // namespace mozilla |