diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /hal | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'hal')
65 files changed, 14440 insertions, 0 deletions
diff --git a/hal/Hal.cpp b/hal/Hal.cpp new file mode 100644 index 000000000..44be2f510 --- /dev/null +++ b/hal/Hal.cpp @@ -0,0 +1,1003 @@ +/* -*- 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 "nsIDOMDocument.h" +#include "nsIDOMWindow.h" +#include "nsIDocument.h" +#include "nsIDocShell.h" +#include "nsITabChild.h" +#include "nsIWebNavigation.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "nsPIDOMWindow.h" +#include "nsJSUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Observer.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ScreenOrientation.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 { +namespace hal { + +mozilla::LogModule * +GetHalLog() +{ + static mozilla::LazyLogModule sHalLog("hal"); + return sHalLog; +} + +namespace { + +void +AssertMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +bool +InSandbox() +{ + return GeckoProcessType_Content == XRE_GetProcessType(); +} + +void +AssertMainProcess() +{ + MOZ_ASSERT(GeckoProcessType_Default == XRE_GetProcessType()); +} + +#if !defined(MOZ_WIDGET_GONK) + +bool +WindowIsActive(nsPIDOMWindowInner* aWindow) +{ + nsIDocument* document = aWindow->GetDoc(); + NS_ENSURE_TRUE(document, false); + + return !document->Hidden(); +} + +#endif // !defined(MOZ_WIDGET_GONK) + +StaticAutoPtr<WindowIdentifier::IDArrayType> gLastIDToVibrate; + +void InitLastIDToVibrate() +{ + gLastIDToVibrate = new WindowIdentifier::IDArrayType(); + ClearOnShutdown(&gLastIDToVibrate); +} + +} // namespace + +void +Vibrate(const nsTArray<uint32_t>& pattern, nsPIDOMWindowInner* window) +{ + Vibrate(pattern, WindowIdentifier(window)); +} + +void +Vibrate(const nsTArray<uint32_t>& pattern, const WindowIdentifier &id) +{ + AssertMainThread(); + +#if !defined(MOZ_WIDGET_GONK) + // 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; + } +#endif // !defined(MOZ_WIDGET_GONK) + + if (!InSandbox()) { + if (!gLastIDToVibrate) { + InitLastIDToVibrate(); + } + *gLastIDToVibrate = id.AsArray(); + } + + // 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() ? id : WindowIdentifier())); +} + +void +CancelVibrate(nsPIDOMWindowInner* window) +{ + CancelVibrate(WindowIdentifier(window)); +} + +void +CancelVibrate(const WindowIdentifier &id) +{ + AssertMainThread(); + + // 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. + + if (InSandbox() || (gLastIDToVibrate && *gLastIDToVibrate == id.AsArray())) { + // 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() ? id : WindowIdentifier())); + } +} + +template <class InfoType> +class ObserversManager +{ +public: + void AddObserver(Observer<InfoType>* aObserver) { + if (!mObservers) { + mObservers = new mozilla::ObserverList<InfoType>(); + } + + mObservers->AddObserver(aObserver); + + if (mObservers->Length() == 1) { + EnableNotifications(); + } + } + + void RemoveObserver(Observer<InfoType>* aObserver) { + bool removed = mObservers && mObservers->RemoveObserver(aObserver); + if (!removed) { + return; + } + + if (mObservers->Length() == 0) { + DisableNotifications(); + + OnNotificationsDisabled(); + + delete mObservers; + mObservers = nullptr; + } + } + + void BroadcastInformation(const InfoType& aInfo) { + // It is possible for mObservers to be nullptr here on some platforms, + // because a call to BroadcastInformation gets queued up asynchronously + // while RemoveObserver is running (and before the notifications are + // disabled). The queued call can then get run after mObservers has + // been nulled out. See bug 757025. + if (!mObservers) { + return; + } + mObservers->Broadcast(aInfo); + } + +protected: + 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; + + virtual void OnNotificationsDisabled() { + mHasValidCache = false; + } + +private: + InfoType mInfo; + bool mHasValidCache; +}; + +class BatteryObserversManager : public CachingObserversManager<BatteryInformation> +{ +protected: + void EnableNotifications() { + PROXY_IF_SANDBOXED(EnableBatteryNotifications()); + } + + void DisableNotifications() { + PROXY_IF_SANDBOXED(DisableBatteryNotifications()); + } + + void GetCurrentInformationInternal(BatteryInformation* aInfo) { + PROXY_IF_SANDBOXED(GetCurrentBatteryInformation(aInfo)); + } +}; + +static BatteryObserversManager& +BatteryObservers() +{ + static BatteryObserversManager sBatteryObservers; + AssertMainThread(); + return sBatteryObservers; +} + +class NetworkObserversManager : public CachingObserversManager<NetworkInformation> +{ +protected: + void EnableNotifications() { + PROXY_IF_SANDBOXED(EnableNetworkNotifications()); + } + + void DisableNotifications() { + PROXY_IF_SANDBOXED(DisableNetworkNotifications()); + } + + void GetCurrentInformationInternal(NetworkInformation* aInfo) { + PROXY_IF_SANDBOXED(GetCurrentNetworkInformation(aInfo)); + } +}; + +static NetworkObserversManager& +NetworkObservers() +{ + static NetworkObserversManager sNetworkObservers; + AssertMainThread(); + return sNetworkObservers; +} + +class WakeLockObserversManager : public ObserversManager<WakeLockInformation> +{ +protected: + void EnableNotifications() { + PROXY_IF_SANDBOXED(EnableWakeLockNotifications()); + } + + void DisableNotifications() { + PROXY_IF_SANDBOXED(DisableWakeLockNotifications()); + } +}; + +static WakeLockObserversManager& +WakeLockObservers() +{ + static WakeLockObserversManager sWakeLockObservers; + AssertMainThread(); + return sWakeLockObservers; +} + +class ScreenConfigurationObserversManager : public CachingObserversManager<ScreenConfiguration> +{ +protected: + void EnableNotifications() { + PROXY_IF_SANDBOXED(EnableScreenConfigurationNotifications()); + } + + void DisableNotifications() { + PROXY_IF_SANDBOXED(DisableScreenConfigurationNotifications()); + } + + void GetCurrentInformationInternal(ScreenConfiguration* aInfo) { + PROXY_IF_SANDBOXED(GetCurrentScreenConfiguration(aInfo)); + } +}; + +static ScreenConfigurationObserversManager& +ScreenConfigurationObservers() +{ + AssertMainThread(); + static ScreenConfigurationObserversManager sScreenConfigurationObservers; + return sScreenConfigurationObservers; +} + +void +RegisterBatteryObserver(BatteryObserver* aObserver) +{ + AssertMainThread(); + BatteryObservers().AddObserver(aObserver); +} + +void +UnregisterBatteryObserver(BatteryObserver* aObserver) +{ + AssertMainThread(); + BatteryObservers().RemoveObserver(aObserver); +} + +void +GetCurrentBatteryInformation(BatteryInformation* aInfo) +{ + AssertMainThread(); + *aInfo = BatteryObservers().GetCurrentInformation(); +} + +void +NotifyBatteryChange(const BatteryInformation& aInfo) +{ + AssertMainThread(); + BatteryObservers().CacheInformation(aInfo); + BatteryObservers().BroadcastCachedInformation(); +} + +bool GetScreenEnabled() +{ + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(GetScreenEnabled(), false); +} + +void SetScreenEnabled(bool aEnabled) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(SetScreenEnabled(aEnabled)); +} + +bool GetKeyLightEnabled() +{ + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(GetKeyLightEnabled(), false); +} + +void SetKeyLightEnabled(bool aEnabled) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(SetKeyLightEnabled(aEnabled)); +} + +bool GetCpuSleepAllowed() +{ + // Generally for interfaces that are accessible by normal web content + // we should cache the result and be notified on state changes, like + // what the battery API does. But since this is only used by + // privileged interface, the synchronous getter is OK here. + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(GetCpuSleepAllowed(), true); +} + +void SetCpuSleepAllowed(bool aAllowed) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(SetCpuSleepAllowed(aAllowed)); +} + +double GetScreenBrightness() +{ + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(GetScreenBrightness(), 0); +} + +void SetScreenBrightness(double aBrightness) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(SetScreenBrightness(clamped(aBrightness, 0.0, 1.0))); +} + +class SystemClockChangeObserversManager : public ObserversManager<int64_t> +{ +protected: + void EnableNotifications() { + PROXY_IF_SANDBOXED(EnableSystemClockChangeNotifications()); + } + + void DisableNotifications() { + PROXY_IF_SANDBOXED(DisableSystemClockChangeNotifications()); + } +}; + +static SystemClockChangeObserversManager& +SystemClockChangeObservers() +{ + static SystemClockChangeObserversManager sSystemClockChangeObservers; + AssertMainThread(); + return sSystemClockChangeObservers; +} + +void +RegisterSystemClockChangeObserver(SystemClockChangeObserver* aObserver) +{ + AssertMainThread(); + SystemClockChangeObservers().AddObserver(aObserver); +} + +void +UnregisterSystemClockChangeObserver(SystemClockChangeObserver* aObserver) +{ + AssertMainThread(); + SystemClockChangeObservers().RemoveObserver(aObserver); +} + +void +NotifySystemClockChange(const int64_t& aClockDeltaMS) +{ + SystemClockChangeObservers().BroadcastInformation(aClockDeltaMS); +} + +class SystemTimezoneChangeObserversManager : public ObserversManager<SystemTimezoneChangeInformation> +{ +protected: + void EnableNotifications() { + PROXY_IF_SANDBOXED(EnableSystemTimezoneChangeNotifications()); + } + + void DisableNotifications() { + PROXY_IF_SANDBOXED(DisableSystemTimezoneChangeNotifications()); + } +}; + +static SystemTimezoneChangeObserversManager& +SystemTimezoneChangeObservers() +{ + static SystemTimezoneChangeObserversManager sSystemTimezoneChangeObservers; + return sSystemTimezoneChangeObservers; +} + +void +RegisterSystemTimezoneChangeObserver(SystemTimezoneChangeObserver* aObserver) +{ + AssertMainThread(); + SystemTimezoneChangeObservers().AddObserver(aObserver); +} + +void +UnregisterSystemTimezoneChangeObserver(SystemTimezoneChangeObserver* aObserver) +{ + AssertMainThread(); + SystemTimezoneChangeObservers().RemoveObserver(aObserver); +} + +void +NotifySystemTimezoneChange(const SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo) +{ + nsJSUtils::ResetTimeZone(); + SystemTimezoneChangeObservers().BroadcastInformation(aSystemTimezoneChangeInfo); +} + +void +AdjustSystemClock(int64_t aDeltaMilliseconds) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(AdjustSystemClock(aDeltaMilliseconds)); +} + +void +SetTimezone(const nsCString& aTimezoneSpec) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(SetTimezone(aTimezoneSpec)); +} + +int32_t +GetTimezoneOffset() +{ + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(GetTimezoneOffset(), 0); +} + +nsCString +GetTimezone() +{ + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(GetTimezone(), nsCString("")); +} + +void +EnableSensorNotifications(SensorType aSensor) { + AssertMainThread(); + PROXY_IF_SANDBOXED(EnableSensorNotifications(aSensor)); +} + +void +DisableSensorNotifications(SensorType aSensor) { + AssertMainThread(); + PROXY_IF_SANDBOXED(DisableSensorNotifications(aSensor)); +} + +typedef mozilla::ObserverList<SensorData> SensorObserverList; +static SensorObserverList* gSensorObservers = nullptr; + +static SensorObserverList & +GetSensorObservers(SensorType sensor_type) { + MOZ_ASSERT(sensor_type < NUM_SENSOR_TYPE); + + if(!gSensorObservers) { + gSensorObservers = new SensorObserverList[NUM_SENSOR_TYPE]; + } + return gSensorObservers[sensor_type]; +} + +void +RegisterSensorObserver(SensorType aSensor, ISensorObserver *aObserver) { + SensorObserverList &observers = GetSensorObservers(aSensor); + + AssertMainThread(); + + observers.AddObserver(aObserver); + if(observers.Length() == 1) { + EnableSensorNotifications(aSensor); + } +} + +void +UnregisterSensorObserver(SensorType aSensor, ISensorObserver *aObserver) { + AssertMainThread(); + + if (!gSensorObservers) { + return; + } + + SensorObserverList &observers = GetSensorObservers(aSensor); + if (!observers.RemoveObserver(aObserver) || observers.Length() > 0) { + return; + } + DisableSensorNotifications(aSensor); + + // Destroy sSensorObservers only if all observer lists are empty. + for (int i = 0; i < NUM_SENSOR_TYPE; i++) { + if (gSensorObservers[i].Length() > 0) { + return; + } + } + delete [] gSensorObservers; + gSensorObservers = nullptr; +} + +void +NotifySensorChange(const SensorData &aSensorData) { + SensorObserverList &observers = GetSensorObservers(aSensorData.sensor()); + + AssertMainThread(); + + observers.Broadcast(aSensorData); +} + +void +RegisterNetworkObserver(NetworkObserver* aObserver) +{ + AssertMainThread(); + NetworkObservers().AddObserver(aObserver); +} + +void +UnregisterNetworkObserver(NetworkObserver* aObserver) +{ + AssertMainThread(); + NetworkObservers().RemoveObserver(aObserver); +} + +void +GetCurrentNetworkInformation(NetworkInformation* aInfo) +{ + AssertMainThread(); + *aInfo = NetworkObservers().GetCurrentInformation(); +} + +void +NotifyNetworkChange(const NetworkInformation& aInfo) +{ + NetworkObservers().CacheInformation(aInfo); + NetworkObservers().BroadcastCachedInformation(); +} + +void Reboot() +{ + AssertMainProcess(); + AssertMainThread(); + PROXY_IF_SANDBOXED(Reboot()); +} + +void PowerOff() +{ + AssertMainProcess(); + AssertMainThread(); + PROXY_IF_SANDBOXED(PowerOff()); +} + +void StartForceQuitWatchdog(ShutdownMode aMode, int32_t aTimeoutSecs) +{ + AssertMainProcess(); + AssertMainThread(); + PROXY_IF_SANDBOXED(StartForceQuitWatchdog(aMode, aTimeoutSecs)); +} + +void +RegisterWakeLockObserver(WakeLockObserver* aObserver) +{ + AssertMainThread(); + WakeLockObservers().AddObserver(aObserver); +} + +void +UnregisterWakeLockObserver(WakeLockObserver* aObserver) +{ + AssertMainThread(); + WakeLockObservers().RemoveObserver(aObserver); +} + +void +ModifyWakeLock(const nsAString& aTopic, + WakeLockControl aLockAdjust, + WakeLockControl aHiddenAdjust, + uint64_t aProcessID /* = CONTENT_PROCESS_ID_UNKNOWN */) +{ + AssertMainThread(); + + if (aProcessID == CONTENT_PROCESS_ID_UNKNOWN) { + aProcessID = InSandbox() ? ContentChild::GetSingleton()->GetID() : + CONTENT_PROCESS_ID_MAIN; + } + + PROXY_IF_SANDBOXED(ModifyWakeLock(aTopic, aLockAdjust, + aHiddenAdjust, aProcessID)); +} + +void +GetWakeLockInfo(const nsAString& aTopic, WakeLockInformation* aWakeLockInfo) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(GetWakeLockInfo(aTopic, aWakeLockInfo)); +} + +void +NotifyWakeLockChange(const WakeLockInformation& aInfo) +{ + AssertMainThread(); + WakeLockObservers().BroadcastInformation(aInfo); +} + +void +RegisterScreenConfigurationObserver(ScreenConfigurationObserver* aObserver) +{ + AssertMainThread(); + ScreenConfigurationObservers().AddObserver(aObserver); +} + +void +UnregisterScreenConfigurationObserver(ScreenConfigurationObserver* aObserver) +{ + AssertMainThread(); + ScreenConfigurationObservers().RemoveObserver(aObserver); +} + +void +GetCurrentScreenConfiguration(ScreenConfiguration* aScreenConfiguration) +{ + AssertMainThread(); + *aScreenConfiguration = ScreenConfigurationObservers().GetCurrentInformation(); +} + +void +NotifyScreenConfigurationChange(const ScreenConfiguration& aScreenConfiguration) +{ + ScreenConfigurationObservers().CacheInformation(aScreenConfiguration); + ScreenConfigurationObservers().BroadcastCachedInformation(); +} + +bool +LockScreenOrientation(const dom::ScreenOrientationInternal& aOrientation) +{ + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(LockScreenOrientation(aOrientation), false); +} + +void +UnlockScreenOrientation() +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(UnlockScreenOrientation()); +} + +void +EnableSwitchNotifications(SwitchDevice aDevice) { + AssertMainThread(); + PROXY_IF_SANDBOXED(EnableSwitchNotifications(aDevice)); +} + +void +DisableSwitchNotifications(SwitchDevice aDevice) { + AssertMainThread(); + PROXY_IF_SANDBOXED(DisableSwitchNotifications(aDevice)); +} + +SwitchState GetCurrentSwitchState(SwitchDevice aDevice) +{ + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(GetCurrentSwitchState(aDevice), SWITCH_STATE_UNKNOWN); +} + +void NotifySwitchStateFromInputDevice(SwitchDevice aDevice, SwitchState aState) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(NotifySwitchStateFromInputDevice(aDevice, aState)); +} + +typedef mozilla::ObserverList<SwitchEvent> SwitchObserverList; + +static SwitchObserverList *sSwitchObserverLists = nullptr; + +static SwitchObserverList& +GetSwitchObserverList(SwitchDevice aDevice) { + MOZ_ASSERT(0 <= aDevice && aDevice < NUM_SWITCH_DEVICE); + if (sSwitchObserverLists == nullptr) { + sSwitchObserverLists = new SwitchObserverList[NUM_SWITCH_DEVICE]; + } + return sSwitchObserverLists[aDevice]; +} + +static void +ReleaseObserversIfNeeded() { + for (int i = 0; i < NUM_SWITCH_DEVICE; i++) { + if (sSwitchObserverLists[i].Length() != 0) + return; + } + + //The length of every list is 0, no observer in the list. + delete [] sSwitchObserverLists; + sSwitchObserverLists = nullptr; +} + +void +RegisterSwitchObserver(SwitchDevice aDevice, SwitchObserver *aObserver) +{ + AssertMainThread(); + SwitchObserverList& observer = GetSwitchObserverList(aDevice); + observer.AddObserver(aObserver); + if (observer.Length() == 1) { + EnableSwitchNotifications(aDevice); + } +} + +void +UnregisterSwitchObserver(SwitchDevice aDevice, SwitchObserver *aObserver) +{ + AssertMainThread(); + + if (!sSwitchObserverLists) { + return; + } + + SwitchObserverList& observer = GetSwitchObserverList(aDevice); + if (!observer.RemoveObserver(aObserver) || observer.Length() > 0) { + return; + } + + DisableSwitchNotifications(aDevice); + ReleaseObserversIfNeeded(); +} + +void +NotifySwitchChange(const SwitchEvent& aEvent) +{ + // When callback this notification, main thread may call unregister function + // first. We should check if this pointer is valid. + if (!sSwitchObserverLists) + return; + + SwitchObserverList& observer = GetSwitchObserverList(aEvent.device()); + observer.Broadcast(aEvent); +} + +static AlarmObserver* sAlarmObserver; + +bool +RegisterTheOneAlarmObserver(AlarmObserver* aObserver) +{ + MOZ_ASSERT(!InSandbox()); + MOZ_ASSERT(!sAlarmObserver); + + sAlarmObserver = aObserver; + RETURN_PROXY_IF_SANDBOXED(EnableAlarm(), false); +} + +void +UnregisterTheOneAlarmObserver() +{ + if (sAlarmObserver) { + sAlarmObserver = nullptr; + PROXY_IF_SANDBOXED(DisableAlarm()); + } +} + +void +NotifyAlarmFired() +{ + if (sAlarmObserver) { + sAlarmObserver->Notify(void_t()); + } +} + +bool +SetAlarm(int32_t aSeconds, int32_t aNanoseconds) +{ + // It's pointless to program an alarm nothing is going to observe ... + MOZ_ASSERT(sAlarmObserver); + RETURN_PROXY_IF_SANDBOXED(SetAlarm(aSeconds, aNanoseconds), false); +} + +void +SetProcessPriority(int aPid, ProcessPriority aPriority, uint32_t aLRU) +{ + // n.b. The sandboxed implementation crashes; SetProcessPriority works only + // from the main process. + PROXY_IF_SANDBOXED(SetProcessPriority(aPid, aPriority, aLRU)); +} + +void +SetCurrentThreadPriority(hal::ThreadPriority aThreadPriority) +{ + PROXY_IF_SANDBOXED(SetCurrentThreadPriority(aThreadPriority)); +} + +void +SetThreadPriority(PlatformThreadId aThreadId, + hal::ThreadPriority aThreadPriority) +{ + PROXY_IF_SANDBOXED(SetThreadPriority(aThreadId, aThreadPriority)); +} + +// From HalTypes.h. +const char* +ProcessPriorityToString(ProcessPriority aPriority) +{ + switch (aPriority) { + case PROCESS_PRIORITY_MASTER: + return "MASTER"; + 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 "???"; + } +} + +const char * +ThreadPriorityToString(ThreadPriority aPriority) +{ + switch (aPriority) { + case THREAD_PRIORITY_COMPOSITOR: + return "COMPOSITOR"; + default: + MOZ_ASSERT(false); + return "???"; + } +} + +void FactoryReset(mozilla::dom::FactoryResetReason& aReason) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(FactoryReset(aReason)); +} + +void +StartDiskSpaceWatcher() +{ + AssertMainProcess(); + AssertMainThread(); + PROXY_IF_SANDBOXED(StartDiskSpaceWatcher()); +} + +void +StopDiskSpaceWatcher() +{ + AssertMainProcess(); + AssertMainThread(); + PROXY_IF_SANDBOXED(StopDiskSpaceWatcher()); +} + +uint32_t +GetTotalSystemMemory() +{ + return hal_impl::GetTotalSystemMemory(); +} + +bool IsHeadphoneEventFromInputDev() +{ + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(IsHeadphoneEventFromInputDev(), false); +} + +nsresult StartSystemService(const char* aSvcName, const char* aArgs) +{ + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(StartSystemService(aSvcName, aArgs), NS_ERROR_FAILURE); +} + +void StopSystemService(const char* aSvcName) +{ + AssertMainThread(); + PROXY_IF_SANDBOXED(StopSystemService(aSvcName)); +} + +bool SystemServiceIsRunning(const char* aSvcName) +{ + AssertMainThread(); + RETURN_PROXY_IF_SANDBOXED(SystemServiceIsRunning(aSvcName), false); +} + +} // namespace hal +} // namespace mozilla diff --git a/hal/Hal.h b/hal/Hal.h new file mode 100644 index 000000000..e45bbcde2 --- /dev/null +++ b/hal/Hal.h @@ -0,0 +1,560 @@ +/* -*- 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/basictypes.h" +#include "base/platform_thread.h" +#include "nsTArray.h" +#include "mozilla/dom/battery/Types.h" +#include "mozilla/dom/MozPowerManagerBinding.h" +#include "mozilla/dom/network/Types.h" +#include "mozilla/dom/power/Types.h" +#include "mozilla/dom/ScreenOrientation.h" +#include "mozilla/hal_sandbox/PHal.h" +#include "mozilla/HalScreenConfiguration.h" +#include "mozilla/HalTypes.h" +#include "mozilla/Observer.h" +#include "mozilla/Types.h" + +/* + * 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 { + +typedef Observer<void_t> AlarmObserver; + +class WindowIdentifier; + +typedef Observer<int64_t> SystemClockChangeObserver; +typedef Observer<SystemTimezoneChangeInformation> SystemTimezoneChangeObserver; + +} // namespace hal + +namespace MOZ_HAL_NAMESPACE { + +/** + * 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, + const 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(const hal::WindowIdentifier &id); + +/** + * Inform the battery backend there is a new battery observer. + * @param aBatteryObserver The observer that should be added. + */ +void RegisterBatteryObserver(BatteryObserver* aBatteryObserver); + +/** + * Inform the battery backend a battery observer unregistered. + * @param aBatteryObserver The observer that should be removed. + */ +void UnregisterBatteryObserver(BatteryObserver* aBatteryObserver); + +/** + * 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); + +/** + * Determine whether the device's screen is currently enabled. + */ +bool GetScreenEnabled(); + +/** + * Enable or disable the device's screen. + * + * Note that it may take a few seconds for the screen to turn on or off. + */ +void SetScreenEnabled(bool aEnabled); + +/** + * Determine whether the device's keypad/button backlight is currently enabled. + */ +bool GetKeyLightEnabled(); + +/** + * Enable or disable the device's keypad/button backlight. + */ +void SetKeyLightEnabled(bool aEnabled); + +/** + * Get the brightness of the device's screen's backlight, on a scale from 0 + * (very dim) to 1 (full blast). + * + * If the display is currently disabled, this returns the brightness the + * backlight will have when the display is re-enabled. + */ +double GetScreenBrightness(); + +/** + * Set the brightness of the device's screen's backlight, on a scale from 0 + * (very dimm) to 1 (full blast). Values larger than 1 are treated like 1, and + * values smaller than 0 are treated like 0. + * + * Note that we may reduce the resolution of the given brightness value before + * sending it to the screen. Therefore if you call SetScreenBrightness(x) + * followed by GetScreenBrightness(), the value returned by + * GetScreenBrightness() may not be exactly x. + */ +void SetScreenBrightness(double aBrightness); + +/** + * Determine whether the device is allowed to sleep. + */ +bool GetCpuSleepAllowed(); + +/** + * Set whether the device is allowed to suspend automatically after + * the screen is disabled. + */ +void SetCpuSleepAllowed(bool aAllowed); + +/** + * 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); + + +/** + * Inform the network backend there is a new network observer. + * @param aNetworkObserver The observer that should be added. + */ +void RegisterNetworkObserver(NetworkObserver* aNetworkObserver); + +/** + * Inform the network backend a network observer unregistered. + * @param aNetworkObserver The observer that should be removed. + */ +void UnregisterNetworkObserver(NetworkObserver* aNetworkObserver); + +/** + * 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); + +/** + * Adjusting system clock. + * @param aDeltaMilliseconds The difference compared with current system clock. + */ +void AdjustSystemClock(int64_t aDeltaMilliseconds); + +/** + * Set timezone + * @param aTimezoneSpec The definition can be found in + * http://en.wikipedia.org/wiki/List_of_tz_database_time_zones + */ +void SetTimezone(const nsCString& aTimezoneSpec); + +/** + * Get timezone + * http://en.wikipedia.org/wiki/List_of_tz_database_time_zones + */ +nsCString GetTimezone(); + +/** + * Get timezone offset + * returns the timezone offset relative to UTC in minutes (DST effect included) + */ +int32_t GetTimezoneOffset(); + +/** + * Register observer for system clock changed notification. + * @param aObserver The observer that should be added. + */ +void RegisterSystemClockChangeObserver( + hal::SystemClockChangeObserver* aObserver); + +/** + * Unregister the observer for system clock changed. + * @param aObserver The observer that should be removed. + */ +void UnregisterSystemClockChangeObserver( + hal::SystemClockChangeObserver* aObserver); + +/** + * Notify of a change in the system clock. + * @param aClockDeltaMS + */ +void NotifySystemClockChange(const int64_t& aClockDeltaMS); + +/** + * Register observer for system timezone changed notification. + * @param aObserver The observer that should be added. + */ +void RegisterSystemTimezoneChangeObserver( + hal::SystemTimezoneChangeObserver* aObserver); + +/** + * Unregister the observer for system timezone changed. + * @param aObserver The observer that should be removed. + */ +void UnregisterSystemTimezoneChangeObserver( + hal::SystemTimezoneChangeObserver* aObserver); + +/** + * Notify of a change in the system timezone. + * @param aSystemTimezoneChangeInfo + */ +void NotifySystemTimezoneChange( + const hal::SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo); + +/** + * Reboot the device. + * + * This API is currently only allowed to be used from the main process. + */ +void Reboot(); + +/** + * Power off the device. + * + * This API is currently only allowed to be used from the main process. + */ +void PowerOff(); + +/** + * 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(); + +/** + * Inform the wake lock backend there is a new wake lock observer. + * @param aWakeLockObserver The observer that should be added. + */ +void RegisterWakeLockObserver(WakeLockObserver* aObserver); + +/** + * Inform the wake lock backend a wake lock observer unregistered. + * @param aWakeLockObserver The observer that should be removed. + */ +void UnregisterWakeLockObserver(WakeLockObserver* aObserver); + +/** + * Adjust a wake lock's counts on behalf of a given process. + * + * In most cases, you shouldn't need to pass the aProcessID argument; the + * default of CONTENT_PROCESS_ID_UNKNOWN is probably what you want. + * + * @param aTopic lock topic + * @param aLockAdjust to increase or decrease active locks + * @param aHiddenAdjust to increase or decrease hidden locks + * @param aProcessID indicates which process we're modifying the wake lock + * on behalf of. It is interpreted as + * + * CONTENT_PROCESS_ID_UNKNOWN: The current process + * CONTENT_PROCESS_ID_MAIN: The root process + * X: The process with ContentChild::GetID() == X + */ +void ModifyWakeLock(const nsAString &aTopic, + hal::WakeLockControl aLockAdjust, + hal::WakeLockControl aHiddenAdjust, + uint64_t aProcessID = hal::CONTENT_PROCESS_ID_UNKNOWN); + +/** + * 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); + +/** + * Inform the backend there is a new screen configuration observer. + * @param aScreenConfigurationObserver The observer that should be added. + */ +void RegisterScreenConfigurationObserver(hal::ScreenConfigurationObserver* aScreenConfigurationObserver); + +/** + * Inform the backend a screen configuration observer unregistered. + * @param aScreenConfigurationObserver The observer that should be removed. + */ +void UnregisterScreenConfigurationObserver(hal::ScreenConfigurationObserver* aScreenConfigurationObserver); + +/** + * Returns the current screen configuration. + */ +void GetCurrentScreenConfiguration(hal::ScreenConfiguration* aScreenConfiguration); + +/** + * Notify of a change in the screen configuration. + * @param aScreenConfiguration The new screen orientation. + */ +void NotifyScreenConfigurationChange(const hal::ScreenConfiguration& aScreenConfiguration); + +/** + * Lock the screen orientation to the specific orientation. + * @return Whether the lock has been accepted. + */ +MOZ_MUST_USE bool LockScreenOrientation(const dom::ScreenOrientationInternal& aOrientation); + +/** + * Unlock the screen orientation. + */ +void UnlockScreenOrientation(); + +/** + * Register an observer for the switch of given SwitchDevice. + * + * The observer will receive data whenever the data generated by the + * given switch. + */ +void RegisterSwitchObserver(hal::SwitchDevice aDevice, hal::SwitchObserver *aSwitchObserver); + +/** + * Unregister an observer for the switch of given SwitchDevice. + */ +void UnregisterSwitchObserver(hal::SwitchDevice aDevice, hal::SwitchObserver *aSwitchObserver); + +/** + * Notify the state of the switch. + * + * This API is internal to hal; clients shouldn't call it directly. + */ +void NotifySwitchChange(const hal::SwitchEvent& aEvent); + +/** + * Get current switch information. + */ +hal::SwitchState GetCurrentSwitchState(hal::SwitchDevice aDevice); + +/** + * Notify switch status change from input device. + */ +void NotifySwitchStateFromInputDevice(hal::SwitchDevice aDevice, + hal::SwitchState aState); + +/** + * Register an observer that is notified when a programmed alarm + * expires. + * + * Currently, there can only be 0 or 1 alarm observers. + */ +MOZ_MUST_USE bool RegisterTheOneAlarmObserver(hal::AlarmObserver* aObserver); + +/** + * Unregister the alarm observer. Doing so will implicitly cancel any + * programmed alarm. + */ +void UnregisterTheOneAlarmObserver(); + +/** + * Notify that the programmed alarm has expired. + * + * This API is internal to hal; clients shouldn't call it directly. + */ +void NotifyAlarmFired(); + +/** + * Program the real-time clock to expire at the time |aSeconds|, + * |aNanoseconds|. These specify a point in real time relative to the + * UNIX epoch. The alarm will fire at this time point even if the + * real-time clock is changed; that is, this alarm respects changes to + * the real-time clock. Return true iff the alarm was programmed. + * + * The alarm can be reprogrammed at any time. + * + * This API is currently only allowed to be used from non-sandboxed + * contexts. + */ +MOZ_MUST_USE bool SetAlarm(int32_t aSeconds, int32_t aNanoseconds); + +/** + * 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, + uint32_t aLRU = 0); + + +/** + * Set the current thread's priority to appropriate platform-specific value for + * given functionality. Instead of providing arbitrary priority numbers you + * must specify a type of function like THREAD_PRIORITY_COMPOSITOR. + */ +void SetCurrentThreadPriority(hal::ThreadPriority aThreadPriority); + +/** + * Set a thread priority to appropriate platform-specific value for + * given functionality. Instead of providing arbitrary priority numbers you + * must specify a type of function like THREAD_PRIORITY_COMPOSITOR. + */ +void SetThreadPriority(PlatformThreadId aThreadId, + hal::ThreadPriority aThreadPriority); + +/** + * Start a watchdog to compulsively shutdown the system if it hangs. + * @param aMode Specify how to shutdown the system. + * @param aTimeoutSecs Specify the delayed seconds to shutdown the system. + * + * This API is currently only allowed to be used from the main process. + */ +void StartForceQuitWatchdog(hal::ShutdownMode aMode, int32_t aTimeoutSecs); + +/** + * Perform Factory Reset to wipe out all user data. + */ +void FactoryReset(mozilla::dom::FactoryResetReason& aReason); + +/** + * Start monitoring disk space for low space situations. + * + * This API is currently only allowed to be used from the main process. + */ +void StartDiskSpaceWatcher(); + +/** + * Stop monitoring disk space for low space situations. + * + * This API is currently only allowed to be used from the main process. + */ +void StopDiskSpaceWatcher(); + +/** + * Get total system memory of device being run on in bytes. + * + * Returns 0 if we are unable to determine this information from /proc/meminfo. + */ +uint32_t GetTotalSystemMemory(); + +/** + * Determine whether the headphone switch event is from input device + */ +bool IsHeadphoneEventFromInputDev(); + +/** + * Start the system service with the specified name and arguments. + */ +nsresult StartSystemService(const char* aSvcName, const char* aArgs); + +/** + * Stop the system service with the specified name. + */ +void StopSystemService(const char* aSvcName); + +/** + * Determine whether the system service with the specified name is running. + */ +bool SystemServiceIsRunning(const char* aSvcName); + +} // 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/HalImpl.h b/hal/HalImpl.h new file mode 100644 index 000000000..9143cc5de --- /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 000000000..d93023dd0 --- /dev/null +++ b/hal/HalInternal.h @@ -0,0 +1,105 @@ +/* -*- 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(); + +/** + * Enable switch notifications from the backend + */ +void EnableSwitchNotifications(hal::SwitchDevice aDevice); + +/** + * Disable switch notifications from the backend + */ +void DisableSwitchNotifications(hal::SwitchDevice aDevice); + +/** + * Enable alarm notifications from the backend. + */ +MOZ_MUST_USE bool EnableAlarm(); + +/** + * Disable alarm notifications from the backend. + */ +void DisableAlarm(); + +/** + * Enable system clock change notifications from the backend. + */ +void EnableSystemClockChangeNotifications(); + +/** + * Disable system clock change notifications from the backend. + */ +void DisableSystemClockChangeNotifications(); + +/** + * Enable system timezone change notifications from the backend. + */ +void EnableSystemTimezoneChangeNotifications(); + +/** + * Disable system timezone change notifications from the backend. + */ +void DisableSystemTimezoneChangeNotifications(); + +/** + * 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 000000000..0d72e98d0 --- /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/HalSandbox.h b/hal/HalSandbox.h new file mode 100644 index 000000000..255a1aae9 --- /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 000000000..dce84fd31 --- /dev/null +++ b/hal/HalScreenConfiguration.h @@ -0,0 +1,20 @@ +/* -*- 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" + +namespace mozilla { +namespace hal { +class ScreenConfiguration; +typedef Observer<ScreenConfiguration> ScreenConfigurationObserver; +} // namespace hal +} // namespace mozilla + +#endif // mozilla_HalScreenConfiguration_h + diff --git a/hal/HalSensor.h b/hal/HalSensor.h new file mode 100644 index 000000000..551c4271d --- /dev/null +++ b/hal/HalSensor.h @@ -0,0 +1,80 @@ +/* -*- 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_UNKNOWN = -1, + 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; + +/** + * Enumeration of sensor accuracy types. + */ +enum SensorAccuracyType { + SENSOR_ACCURACY_UNKNOWN = -1, + SENSOR_ACCURACY_UNRELIABLE, + SENSOR_ACCURACY_LOW, + SENSOR_ACCURACY_MED, + SENSOR_ACCURACY_HIGH, + NUM_SENSOR_ACCURACY_TYPE +}; + +class SensorAccuracy; + +typedef Observer<SensorAccuracy> ISensorAccuracyObserver; + +} // namespace hal +} // namespace mozilla + +#include "ipc/IPCMessageUtils.h" + +namespace IPC { + /** + * Serializer for SensorType + */ + template <> + struct ParamTraits<mozilla::hal::SensorType>: + public ContiguousEnumSerializer< + mozilla::hal::SensorType, + mozilla::hal::SENSOR_UNKNOWN, + mozilla::hal::NUM_SENSOR_TYPE> { + }; + + template <> + struct ParamTraits<mozilla::hal::SensorAccuracyType>: + public ContiguousEnumSerializer< + mozilla::hal::SensorAccuracyType, + mozilla::hal::SENSOR_ACCURACY_UNKNOWN, + mozilla::hal::NUM_SENSOR_ACCURACY_TYPE> { + + }; +} // namespace IPC + +#endif /* __HAL_SENSOR_H_ */ diff --git a/hal/HalTypes.h b/hal/HalTypes.h new file mode 100644 index 000000000..5f0ad3ff7 --- /dev/null +++ b/hal/HalTypes.h @@ -0,0 +1,177 @@ +/* -*- 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/IPCMessageUtils.h" +#include "mozilla/Observer.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; + +/** + * These are defined by libhardware, specifically, hardware/libhardware/include/hardware/lights.h + * in the gonk subsystem. + * If these change and are exposed to JS, make sure nsIHal.idl is updated as well. + */ +enum ShutdownMode { + eHalShutdownMode_Unknown = -1, + eHalShutdownMode_PowerOff = 0, + eHalShutdownMode_Reboot = 1, + eHalShutdownMode_Restart = 2, + eHalShutdownMode_Count = 3 +}; + +class SwitchEvent; + +enum SwitchDevice { + SWITCH_DEVICE_UNKNOWN = -1, + SWITCH_HEADPHONES, + SWITCH_USB, + NUM_SWITCH_DEVICE +}; + +enum SwitchState { + SWITCH_STATE_UNKNOWN = -1, + SWITCH_STATE_ON, + SWITCH_STATE_OFF, + SWITCH_STATE_HEADSET, // Headphone with microphone + SWITCH_STATE_HEADPHONE, // without microphone + NUM_SWITCH_STATE +}; + +typedef Observer<SwitchEvent> SwitchObserver; + +// 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_MASTER, + NUM_PROCESS_PRIORITY +}; + +/** + * Values that can be passed to hal::SetCurrentThreadPriority(). These should be + * functional in nature, such as COMPOSITOR, instead of levels, like LOW/HIGH. + * This allows us to tune our priority scheme for the system in one place such + * that it makes sense holistically for the overall operating system. On gonk + * or android we may want different priority schemes than on windows, etc. + */ +enum ThreadPriority { + THREAD_PRIORITY_COMPOSITOR, + NUM_THREAD_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); + +/** + * Convert a ThreadPriority 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 assert in debug builds + * and otherwise return "???". + */ +const char * +ThreadPriorityToString(ThreadPriority aPriority); + +/** + * Used by ModifyWakeLock + */ +enum WakeLockControl { + WAKE_LOCK_REMOVE_ONE = -1, + WAKE_LOCK_NO_CHANGE = 0, + WAKE_LOCK_ADD_ONE = 1, + NUM_WAKE_LOCK +}; + +} // namespace hal +} // namespace mozilla + +namespace IPC { + +/** + * Serializer for ShutdownMode. + */ +template <> +struct ParamTraits<mozilla::hal::ShutdownMode> + : public ContiguousEnumSerializer< + mozilla::hal::ShutdownMode, + mozilla::hal::eHalShutdownMode_Unknown, + mozilla::hal::eHalShutdownMode_Count> +{}; + +/** + * WakeLockControl serializer. + */ +template <> +struct ParamTraits<mozilla::hal::WakeLockControl> + : public ContiguousEnumSerializer< + mozilla::hal::WakeLockControl, + mozilla::hal::WAKE_LOCK_REMOVE_ONE, + mozilla::hal::NUM_WAKE_LOCK> +{}; + +/** + * Serializer for SwitchState + */ +template <> +struct ParamTraits<mozilla::hal::SwitchState>: + public ContiguousEnumSerializer< + mozilla::hal::SwitchState, + mozilla::hal::SWITCH_STATE_UNKNOWN, + mozilla::hal::NUM_SWITCH_STATE> { +}; + +/** + * Serializer for SwitchDevice + */ +template <> +struct ParamTraits<mozilla::hal::SwitchDevice>: + public ContiguousEnumSerializer< + mozilla::hal::SwitchDevice, + mozilla::hal::SWITCH_DEVICE_UNKNOWN, + mozilla::hal::NUM_SWITCH_DEVICE> { +}; + +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 000000000..af4dc454e --- /dev/null +++ b/hal/HalWakeLock.cpp @@ -0,0 +1,282 @@ +/* -*- 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 "mozilla/HalWakeLock.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/ContentParent.h" +#include "nsAutoPtr.h" +#include "nsClassHashtable.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsIPropertyBag2.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; + nsTArray<uint64_t> processes; +}; + +typedef nsDataHashtable<nsUint64HashKey, LockCount> ProcessLockTable; +typedef nsClassHashtable<nsStringHashKey, ProcessLockTable> LockTable; + +int sActiveListeners = 0; +StaticAutoPtr<LockTable> sLockTable; +bool sInitialized = false; +bool sIsShuttingDown = false; + +WakeLockInformation +WakeLockInfoFromLockCount(const nsAString& aTopic, const LockCount& aLockCount) +{ + // TODO: Once we abandon b2g18, we can switch this to use the + // WakeLockInformation constructor, which is better because it doesn't let us + // forget to assign a param. For now we have to do it this way, because + // b2g18 doesn't have the nsTArray <--> InfallibleTArray conversion (bug + // 819791). + + WakeLockInformation info; + info.topic() = aTopic; + info.numLocks() = aLockCount.numLocks; + info.numHidden() = aLockCount.numHidden; + info.lockingProcesses().AppendElements(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(NS_LITERAL_STRING("childID"), + &childID); + if (NS_SUCCEEDED(rv)) { + for (auto iter = sLockTable->Iter(); !iter.Done(); iter.Next()) { + nsAutoPtr<ProcessLockTable>& table = iter.Data(); + + 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; +} + +void +Init() +{ + sLockTable = new LockTable(); + sInitialized = true; + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(new ClearHashtableOnShutdown(), "xpcom-shutdown", false); + obs->AddObserver(new CleanupOnContentShutdown(), "ipc:content-shutdown", false); + } +} + +} // namespace + +namespace mozilla { + +namespace hal { + +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 +ModifyWakeLock(const nsAString& aTopic, + hal::WakeLockControl aLockAdjust, + hal::WakeLockControl aHiddenAdjust, + uint64_t aProcessID) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aProcessID != CONTENT_PROCESS_ID_UNKNOWN); + + if (sIsShuttingDown) { + return; + } + if (!sInitialized) { + Init(); + } + + ProcessLockTable* table = sLockTable->Get(aTopic); + LockCount processCount; + LockCount totalCount; + if (!table) { + table = new ProcessLockTable(); + sLockTable->Put(aTopic, table); + } else { + table->Get(aProcessID, &processCount); + CountWakeLocks(table, &totalCount); + } + + 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); + bool processWasLocked = processCount.numLocks > 0; + + processCount.numLocks += aLockAdjust; + processCount.numHidden += aHiddenAdjust; + + totalCount.numLocks += aLockAdjust; + totalCount.numHidden += aHiddenAdjust; + + if (processCount.numLocks) { + table->Put(aProcessID, processCount); + } else { + table->Remove(aProcessID); + } + 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 +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 (!sInitialized) { + Init(); + } + + 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 000000000..7ee6327d8 --- /dev/null +++ b/hal/HalWakeLock.h @@ -0,0 +1,26 @@ +/* -*- 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 mozilla + +#endif /* __HAL_WAKELOCK_H_ */ diff --git a/hal/WindowIdentifier.cpp b/hal/WindowIdentifier.cpp new file mode 100644 index 000000000..8e1354ba7 --- /dev/null +++ b/hal/WindowIdentifier.cpp @@ -0,0 +1,80 @@ +/* -*- 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) + , mIsEmpty(true) +{ +} + +WindowIdentifier::WindowIdentifier(nsPIDOMWindowInner* window) + : mWindow(window) + , mIsEmpty(false) +{ + mID.AppendElement(GetWindowID()); +} + +WindowIdentifier::WindowIdentifier(const InfallibleTArray<uint64_t> &id, + nsPIDOMWindowInner* window) + : mID(id) + , mWindow(window) + , mIsEmpty(false) +{ + mID.AppendElement(GetWindowID()); +} + +WindowIdentifier::WindowIdentifier(const WindowIdentifier &other) + : mID(other.mID) + , mWindow(other.mWindow) + , mIsEmpty(other.mIsEmpty) +{ +} + +const InfallibleTArray<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 000000000..928a16896 --- /dev/null +++ b/hal/WindowIdentifier.h @@ -0,0 +1,111 @@ +/* -*- 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(); + + /** + * Copy constructor. + */ + WindowIdentifier(const WindowIdentifier& other); + + /** + * 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(const InfallibleTArray<uint64_t>& id, + nsPIDOMWindowInner* window); + + /** + * Get the list of window and process IDs we contain. + */ + typedef InfallibleTArray<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; + bool mIsEmpty; +}; + +} // namespace hal +} // namespace mozilla + +#endif // mozilla_hal_WindowIdentifier_h diff --git a/hal/android/AndroidAlarm.cpp b/hal/android/AndroidAlarm.cpp new file mode 100644 index 000000000..3b2613be8 --- /dev/null +++ b/hal/android/AndroidAlarm.cpp @@ -0,0 +1,48 @@ +/* -*- 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 "GeneratedJNINatives.h" +#include "GeneratedJNIWrappers.h" + +using namespace mozilla::hal; + +namespace mozilla { + +class AlarmReceiver : public java::AlarmReceiver::Natives<AlarmReceiver> +{ +private: + AlarmReceiver(); + +public: + static void NotifyAlarmFired() { + hal::NotifyAlarmFired(); + } +}; + +namespace hal_impl { + +bool +EnableAlarm() +{ + AlarmReceiver::Init(); + return true; +} + +void +DisableAlarm() +{ + java::GeckoAppShell::DisableAlarm(); +} + +bool +SetAlarm(int32_t aSeconds, int32_t aNanoseconds) +{ + return java::GeckoAppShell::SetAlarm(aSeconds, aNanoseconds); +} + +} // hal_impl +} // mozilla diff --git a/hal/android/AndroidHal.cpp b/hal/android/AndroidHal.cpp new file mode 100644 index 000000000..495b623ef --- /dev/null +++ b/hal/android/AndroidHal.cpp @@ -0,0 +1,175 @@ +/* -*- 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/dom/ScreenOrientation.h" +#include "nsIScreenManager.h" +#include "nsServiceManagerUtils.h" + +using namespace mozilla::dom; +using namespace mozilla::hal; + +namespace java = mozilla::java; + +namespace mozilla { +namespace hal_impl { + +void +Vibrate(const nsTArray<uint32_t> &pattern, const 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(const 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); +} + +void +EnableScreenConfigurationNotifications() +{ + java::GeckoAppShell::EnableScreenOrientationNotifications(); +} + +void +DisableScreenConfigurationNotifications() +{ + java::GeckoAppShell::DisableScreenOrientationNotifications(); +} + +void +GetCurrentScreenConfiguration(ScreenConfiguration* aScreenConfiguration) +{ + AndroidBridge* bridge = AndroidBridge::Bridge(); + if (!bridge) { + return; + } + + nsresult rv; + nsCOMPtr<nsIScreenManager> screenMgr = + do_GetService("@mozilla.org/gfx/screenmanager;1", &rv); + if (NS_FAILED(rv)) { + NS_ERROR("Can't find nsIScreenManager!"); + return; + } + + nsIntRect rect; + int32_t colorDepth, pixelDepth; + int16_t angle; + ScreenOrientationInternal orientation; + nsCOMPtr<nsIScreen> screen; + + screenMgr->GetPrimaryScreen(getter_AddRefs(screen)); + screen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height); + screen->GetColorDepth(&colorDepth); + screen->GetPixelDepth(&pixelDepth); + orientation = static_cast<ScreenOrientationInternal>(bridge->GetScreenOrientation()); + angle = bridge->GetScreenAngle(); + + *aScreenConfiguration = + hal::ScreenConfiguration(rect, orientation, angle, colorDepth, pixelDepth); +} + +bool +LockScreenOrientation(const ScreenOrientationInternal& aOrientation) +{ + // Force the default orientation to be portrait-primary. + ScreenOrientationInternal orientation = + aOrientation == eScreenOrientation_Default ? eScreenOrientation_PortraitPrimary + : aOrientation; + + switch (orientation) { + // The Android backend only supports these orientations. + case eScreenOrientation_PortraitPrimary: + case eScreenOrientation_PortraitSecondary: + case eScreenOrientation_PortraitPrimary | eScreenOrientation_PortraitSecondary: + case eScreenOrientation_LandscapePrimary: + case eScreenOrientation_LandscapeSecondary: + case eScreenOrientation_LandscapePrimary | eScreenOrientation_LandscapeSecondary: + case eScreenOrientation_Default: + java::GeckoAppShell::LockScreenOrientation(orientation); + return true; + default: + return false; + } +} + +void +UnlockScreenOrientation() +{ + java::GeckoAppShell::UnlockScreenOrientation(); +} + +} // hal_impl +} // mozilla + diff --git a/hal/android/AndroidSensor.cpp b/hal/android/AndroidSensor.cpp new file mode 100644 index 000000000..cb1060342 --- /dev/null +++ b/hal/android/AndroidSensor.cpp @@ -0,0 +1,25 @@ +/* -*- 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 "GeneratedJNIWrappers.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); +} + +} // hal_impl +} // mozilla diff --git a/hal/cocoa/CocoaBattery.cpp b/hal/cocoa/CocoaBattery.cpp new file mode 100644 index 000000000..6f1b7b1dc --- /dev/null +++ b/hal/cocoa/CocoaBattery.cpp @@ -0,0 +1,325 @@ +/* -*- 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() +{ + MOZ_ASSERT(mRunLoopSource, "IOPS Notification Loop Source not set up. " + "(StopListening without BeginListening)"); + + ::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. + int currentCapacity = 0; + const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSCurrentCapacityKey)); + ::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/CocoaSensor.mm b/hal/cocoa/CocoaSensor.mm new file mode 100644 index 000000000..ad203073c --- /dev/null +++ b/hal/cocoa/CocoaSensor.mm @@ -0,0 +1,149 @@ +/* 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); + InfallibleTArray<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::SENSOR_ACCURACY_UNKNOWN); + 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->InitWithFuncCallback(UpdateHandler, + nullptr, + DEFAULT_SENSOR_POLL, + nsITimer::TYPE_REPEATING_SLACK); + } + } +} +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 000000000..2f0b2664e --- /dev/null +++ b/hal/cocoa/smslib.h @@ -0,0 +1,159 @@ +/* + * 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 000000000..c11c1e4d6 --- /dev/null +++ b/hal/cocoa/smslib.mm @@ -0,0 +1,938 @@ +/* + * 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 +// access 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/FallbackAlarm.cpp b/hal/fallback/FallbackAlarm.cpp new file mode 100644 index 000000000..9ec5c48e1 --- /dev/null +++ b/hal/fallback/FallbackAlarm.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 <algorithm> + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace hal_impl { + +static void +TimerCallbackFunc(nsITimer *aTimer, void *aClosure) +{ + hal::NotifyAlarmFired(); +} + +static StaticRefPtr<nsITimer> sTimer; + +bool +EnableAlarm() +{ + static bool initialized = false; + if (!initialized) { + initialized = true; + ClearOnShutdown(&sTimer); + } + + nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1"); + sTimer = timer; + MOZ_ASSERT(sTimer); + return true; +} + +void +DisableAlarm() +{ + /* + * DisableAlarm() may be called after sTimer has been set to null by + * ClearOnShutdown(). + */ + if (sTimer) { + sTimer->Cancel(); + } +} + +bool +SetAlarm(int32_t aSeconds, int32_t aNanoseconds) +{ + if (!sTimer) { + MOZ_ASSERT(false, "We should have enabled the alarm"); + return false; + } + + // Do the math to convert aSeconds and aNanoseconds into milliseconds since + // the epoch. + int64_t milliseconds = static_cast<int64_t>(aSeconds) * 1000 + + static_cast<int64_t>(aNanoseconds) / 1000000; + + // nsITimer expects relative milliseconds. + int64_t relMilliseconds = milliseconds - PR_Now() / 1000; + + // If the alarm time is in the past relative to PR_Now(), + // we choose to immediately fire the alarm. Passing 0 means nsITimer will + // queue a timeout event immediately. + sTimer->InitWithFuncCallback(TimerCallbackFunc, nullptr, + clamped<int64_t>(relMilliseconds, 0, INT32_MAX), + nsITimer::TYPE_ONE_SHOT); + return true; +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackBattery.cpp b/hal/fallback/FallbackBattery.cpp new file mode 100644 index 000000000..3e5e71574 --- /dev/null +++ b/hal/fallback/FallbackBattery.cpp @@ -0,0 +1,30 @@ +/* -*- 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; +} + +} // hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackDiskSpaceWatcher.cpp b/hal/fallback/FallbackDiskSpaceWatcher.cpp new file mode 100644 index 000000000..99d144397 --- /dev/null +++ b/hal/fallback/FallbackDiskSpaceWatcher.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/. */ + +namespace mozilla { +namespace hal_impl { + +void +StartDiskSpaceWatcher() +{ +} + +void +StopDiskSpaceWatcher() +{ +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackFactoryReset.cpp b/hal/fallback/FallbackFactoryReset.cpp new file mode 100644 index 000000000..76a727702 --- /dev/null +++ b/hal/fallback/FallbackFactoryReset.cpp @@ -0,0 +1,18 @@ +/* -*- 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" + +namespace mozilla { +namespace hal_impl { + +void +FactoryReset(mozilla::dom::FactoryResetReason&) +{} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackMemory.cpp b/hal/fallback/FallbackMemory.cpp new file mode 100644 index 000000000..2750eebd8 --- /dev/null +++ b/hal/fallback/FallbackMemory.cpp @@ -0,0 +1,20 @@ +/* -*- 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" + +namespace mozilla { +namespace hal_impl { + +uint32_t +GetTotalSystemMemory() +{ + return 0; +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackNetwork.cpp b/hal/fallback/FallbackNetwork.cpp new file mode 100644 index 000000000..d8995bc6d --- /dev/null +++ b/hal/fallback/FallbackNetwork.cpp @@ -0,0 +1,31 @@ +/* -*- 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/FallbackPower.cpp b/hal/fallback/FallbackPower.cpp new file mode 100644 index 000000000..0a53e89ad --- /dev/null +++ b/hal/fallback/FallbackPower.cpp @@ -0,0 +1,25 @@ +/* -*- 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 { + +void +Reboot() +{} + +void +PowerOff() +{} + +void +StartForceQuitWatchdog(hal::ShutdownMode aMode, int32_t aTimeoutSecs) +{} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackProcessPriority.cpp b/hal/fallback/FallbackProcessPriority.cpp new file mode 100644 index 000000000..b03b1542e --- /dev/null +++ b/hal/fallback/FallbackProcessPriority.cpp @@ -0,0 +1,21 @@ +/* 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, uint32_t aLRU) +{ + HAL_LOG("FallbackProcessPriority - SetProcessPriority(%d, %s, %u)\n", + aPid, ProcessPriorityToString(aPriority), aLRU); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackScreenConfiguration.cpp b/hal/fallback/FallbackScreenConfiguration.cpp new file mode 100644 index 000000000..cbee955fb --- /dev/null +++ b/hal/fallback/FallbackScreenConfiguration.cpp @@ -0,0 +1,63 @@ +/* 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/ScreenOrientation.h" +#include "nsIScreenManager.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { +namespace hal_impl { + +void +EnableScreenConfigurationNotifications() +{ +} + +void +DisableScreenConfigurationNotifications() +{ +} + +void +GetCurrentScreenConfiguration(hal::ScreenConfiguration* aScreenConfiguration) +{ + nsresult rv; + nsCOMPtr<nsIScreenManager> screenMgr = + do_GetService("@mozilla.org/gfx/screenmanager;1", &rv); + if (NS_FAILED(rv)) { + NS_ERROR("Can't find nsIScreenManager!"); + return; + } + + nsIntRect rect; + int32_t colorDepth, pixelDepth; + dom::ScreenOrientationInternal orientation; + nsCOMPtr<nsIScreen> screen; + + screenMgr->GetPrimaryScreen(getter_AddRefs(screen)); + screen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height); + screen->GetColorDepth(&colorDepth); + screen->GetPixelDepth(&pixelDepth); + orientation = rect.width >= rect.height + ? dom::eScreenOrientation_LandscapePrimary + : dom::eScreenOrientation_PortraitPrimary; + + *aScreenConfiguration = + hal::ScreenConfiguration(rect, orientation, 0, colorDepth, pixelDepth); +} + +bool +LockScreenOrientation(const dom::ScreenOrientationInternal& aOrientation) +{ + return false; +} + +void +UnlockScreenOrientation() +{ +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackScreenPower.cpp b/hal/fallback/FallbackScreenPower.cpp new file mode 100644 index 000000000..2a7ad276f --- /dev/null +++ b/hal/fallback/FallbackScreenPower.cpp @@ -0,0 +1,41 @@ +/* -*- 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/. */ + +namespace mozilla { +namespace hal_impl { + +bool +GetScreenEnabled() +{ + return true; +} + +void +SetScreenEnabled(bool aEnabled) +{} + +bool +GetKeyLightEnabled() +{ + return true; +} + +void +SetKeyLightEnabled(bool aEnabled) +{} + +double +GetScreenBrightness() +{ + return 1; +} + +void +SetScreenBrightness(double aBrightness) +{} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackSensor.cpp b/hal/fallback/FallbackSensor.cpp new file mode 100644 index 000000000..22b1a0436 --- /dev/null +++ b/hal/fallback/FallbackSensor.cpp @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set sw=4 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) { +} + +} +} diff --git a/hal/fallback/FallbackSwitch.cpp b/hal/fallback/FallbackSwitch.cpp new file mode 100644 index 000000000..e9b7eab0a --- /dev/null +++ b/hal/fallback/FallbackSwitch.cpp @@ -0,0 +1,39 @@ +/* -*- 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 "mozilla/Hal.h" + +using namespace mozilla::hal; + +namespace mozilla { +namespace hal_impl { + +void +EnableSwitchNotifications(SwitchDevice aDevice) +{ +} + +void +DisableSwitchNotifications(SwitchDevice aDevice) +{ +} + +SwitchState +GetCurrentSwitchState(SwitchDevice aDevice) { + return SWITCH_STATE_UNKNOWN; +} + +void +NotifySwitchStateFromInputDevice(SwitchDevice aDevice, SwitchState aState) +{ +} + +bool IsHeadphoneEventFromInputDev() +{ + return false; +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackSystemService.cpp b/hal/fallback/FallbackSystemService.cpp new file mode 100644 index 000000000..93365c47c --- /dev/null +++ b/hal/fallback/FallbackSystemService.cpp @@ -0,0 +1,35 @@ +/* -*- 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 { + +nsresult +StartSystemService(const char* aSvcName, const char* aArgs) +{ + MOZ_ASSERT(NS_IsMainThread()); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +void +StopSystemService(const char* aSvcName) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +bool +SystemServiceIsRunning(const char* aSvcName) +{ + MOZ_ASSERT(NS_IsMainThread()); + + return false; +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackThreadPriority.cpp b/hal/fallback/FallbackThreadPriority.cpp new file mode 100644 index 000000000..dce5dfa82 --- /dev/null +++ b/hal/fallback/FallbackThreadPriority.cpp @@ -0,0 +1,29 @@ +/* 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 +SetCurrentThreadPriority(ThreadPriority aPriority) +{ + HAL_LOG("FallbackThreadPriority - SetCurrentThreadPriority(%d)\n", + ThreadPriorityToString(aPriority)); +} + +void +SetThreadPriority(PlatformThreadId aThreadId, + ThreadPriority aPriority) +{ + HAL_LOG("FallbackThreadPriority - SetThreadPriority(%d, %d)\n", + aThreadId, ThreadPriorityToString(aPriority)); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackTime.cpp b/hal/fallback/FallbackTime.cpp new file mode 100644 index 000000000..a2c56e817 --- /dev/null +++ b/hal/fallback/FallbackTime.cpp @@ -0,0 +1,54 @@ +/* -*- 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" + +namespace mozilla { +namespace hal_impl { + +void +AdjustSystemClock(int64_t aDeltaMilliseconds) +{} + +void +SetTimezone(const nsCString& aTimezoneSpec) +{} + +nsCString +GetTimezone() +{ + return EmptyCString(); +} + +int32_t +GetTimezoneOffset() +{ + return 0; +} + +void +EnableSystemClockChangeNotifications() +{ +} + +void +DisableSystemClockChangeNotifications() +{ +} + +void +EnableSystemTimezoneChangeNotifications() +{ +} + +void +DisableSystemTimezoneChangeNotifications() +{ +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackVibration.cpp b/hal/fallback/FallbackVibration.cpp new file mode 100644 index 000000000..ffabe9efe --- /dev/null +++ b/hal/fallback/FallbackVibration.cpp @@ -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/. */ + +#include "Hal.h" + +using mozilla::hal::WindowIdentifier; + +namespace mozilla { +namespace hal_impl { + +void +Vibrate(const nsTArray<uint32_t>& pattern, const hal::WindowIdentifier &) +{} + +void +CancelVibrate(const hal::WindowIdentifier &) +{} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/fallback/FallbackWakeLocks.cpp b/hal/fallback/FallbackWakeLocks.cpp new file mode 100644 index 000000000..1fd97a2e6 --- /dev/null +++ b/hal/fallback/FallbackWakeLocks.cpp @@ -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/. */ + +#include "Hal.h" + +namespace mozilla { +namespace hal_impl { + +bool +GetCpuSleepAllowed() +{ + return true; +} + +void +SetCpuSleepAllowed(bool allowed) +{} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/gonk/GonkDiskSpaceWatcher.cpp b/hal/gonk/GonkDiskSpaceWatcher.cpp new file mode 100644 index 000000000..cdc48ef89 --- /dev/null +++ b/hal/gonk/GonkDiskSpaceWatcher.cpp @@ -0,0 +1,324 @@ +/* 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 <sys/syscall.h> +#include <sys/vfs.h> +#include <fcntl.h> +#include <errno.h> +#include "base/message_loop.h" +#include "base/task.h" +#include "DiskSpaceWatcher.h" +#include "fanotify.h" +#include "nsIObserverService.h" +#include "nsIDiskSpaceWatcher.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" + +using namespace mozilla; + +namespace mozilla { namespace hal_impl { class GonkDiskSpaceWatcher; } } + +using namespace mozilla::hal_impl; + +namespace mozilla { +namespace hal_impl { + +// NOTE: this should be unnecessary once we no longer support ICS. +#ifndef __NR_fanotify_init +#if defined(__ARM_EABI__) +#define __NR_fanotify_init 367 +#define __NR_fanotify_mark 368 +#elif defined(__i386__) +#define __NR_fanotify_init 338 +#define __NR_fanotify_mark 339 +#else +#error "Unhandled architecture" +#endif +#endif + +// fanotify_init and fanotify_mark functions are syscalls. +// The user space bits are not part of bionic so we add them here +// as well as fanotify.h +int fanotify_init (unsigned int flags, unsigned int event_f_flags) +{ + return syscall(__NR_fanotify_init, flags, event_f_flags); +} + +// Add, remove, or modify an fanotify mark on a filesystem object. +int fanotify_mark (int fanotify_fd, unsigned int flags, + uint64_t mask, int dfd, const char *pathname) +{ + + // On 32 bits platforms we have to convert the 64 bits mask into + // two 32 bits ints. + if (sizeof(void *) == 4) { + union { + uint64_t _64; + uint32_t _32[2]; + } _mask; + _mask._64 = mask; + return syscall(__NR_fanotify_mark, fanotify_fd, flags, + _mask._32[0], _mask._32[1], dfd, pathname); + } + + return syscall(__NR_fanotify_mark, fanotify_fd, flags, mask, dfd, pathname); +} + +class GonkDiskSpaceWatcher final : public MessageLoopForIO::Watcher +{ +public: + GonkDiskSpaceWatcher(); + ~GonkDiskSpaceWatcher() {}; + + virtual void OnFileCanReadWithoutBlocking(int aFd); + + // We should never write to the fanotify fd. + virtual void OnFileCanWriteWithoutBlocking(int aFd) + { + MOZ_CRASH("Must not write to fanotify fd"); + } + + void DoStart(); + void DoStop(); + +private: + void NotifyUpdate(); + + uint64_t mLowThreshold; + uint64_t mHighThreshold; + TimeDuration mTimeout; + TimeStamp mLastTimestamp; + uint64_t mLastFreeSpace; + uint32_t mSizeDelta; + + bool mIsDiskFull; + uint64_t mFreeSpace; + + int mFd; + MessageLoopForIO::FileDescriptorWatcher mReadWatcher; +}; + +static GonkDiskSpaceWatcher* gHalDiskSpaceWatcher = nullptr; + +#define WATCHER_PREF_LOW "disk_space_watcher.low_threshold" +#define WATCHER_PREF_HIGH "disk_space_watcher.high_threshold" +#define WATCHER_PREF_TIMEOUT "disk_space_watcher.timeout" +#define WATCHER_PREF_SIZE_DELTA "disk_space_watcher.size_delta" + +static const char kWatchedPath[] = "/data"; + +// Helper class to dispatch calls to xpcom on the main thread. +class DiskSpaceNotifier : public Runnable +{ +public: + DiskSpaceNotifier(const bool aIsDiskFull, const uint64_t aFreeSpace) : + mIsDiskFull(aIsDiskFull), + mFreeSpace(aFreeSpace) {} + + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + DiskSpaceWatcher::UpdateState(mIsDiskFull, mFreeSpace); + return NS_OK; + } + +private: + bool mIsDiskFull; + uint64_t mFreeSpace; +}; + +// Helper runnable to delete the watcher on the main thread. +class DiskSpaceCleaner : public Runnable +{ +public: + NS_IMETHOD Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + if (gHalDiskSpaceWatcher) { + delete gHalDiskSpaceWatcher; + gHalDiskSpaceWatcher = nullptr; + } + return NS_OK; + } +}; + +GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() : + mLastFreeSpace(UINT64_MAX), + mIsDiskFull(false), + mFreeSpace(UINT64_MAX), + mFd(-1) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gHalDiskSpaceWatcher == nullptr); + + // Default values: 5MB for low threshold, 10MB for high threshold, and + // a timeout of 5 seconds. + mLowThreshold = Preferences::GetInt(WATCHER_PREF_LOW, 5) * 1024 * 1024; + mHighThreshold = Preferences::GetInt(WATCHER_PREF_HIGH, 10) * 1024 * 1024; + mTimeout = TimeDuration::FromSeconds(Preferences::GetInt(WATCHER_PREF_TIMEOUT, 5)); + mSizeDelta = Preferences::GetInt(WATCHER_PREF_SIZE_DELTA, 1) * 1024 * 1024; +} + +void +GonkDiskSpaceWatcher::DoStart() +{ + NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), + "Not on the correct message loop"); + + mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC | O_LARGEFILE); + if (mFd == -1) { + if (errno == ENOSYS) { + // Don't change these printf_stderr since we need these logs even + // in opt builds. + printf_stderr("Warning: No fanotify support in this device's kernel.\n"); +#if ANDROID_VERSION >= 19 + MOZ_CRASH("Fanotify support must be enabled in the kernel."); +#endif + } else { + printf_stderr("Error calling fanotify_init()"); + } + return; + } + + if (fanotify_mark(mFd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_CLOSE, + 0, kWatchedPath) < 0) { + NS_WARNING("Error calling fanotify_mark"); + close(mFd); + mFd = -1; + return; + } + + if (!MessageLoopForIO::current()->WatchFileDescriptor( + mFd, /* persistent = */ true, + MessageLoopForIO::WATCH_READ, + &mReadWatcher, gHalDiskSpaceWatcher)) { + NS_WARNING("Unable to watch fanotify fd."); + close(mFd); + mFd = -1; + } +} + +void +GonkDiskSpaceWatcher::DoStop() +{ + NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), + "Not on the correct message loop"); + + if (mFd != -1) { + mReadWatcher.StopWatchingFileDescriptor(); + fanotify_mark(mFd, FAN_MARK_FLUSH, 0, 0, kWatchedPath); + close(mFd); + mFd = -1; + } + + // Dispatch the cleanup to the main thread. + nsCOMPtr<nsIRunnable> runnable = new DiskSpaceCleaner(); + NS_DispatchToMainThread(runnable); +} + +// We are called off the main thread, so we proxy first to the main thread +// before calling the xpcom object. +void +GonkDiskSpaceWatcher::NotifyUpdate() +{ + mLastTimestamp = TimeStamp::Now(); + mLastFreeSpace = mFreeSpace; + + nsCOMPtr<nsIRunnable> runnable = + new DiskSpaceNotifier(mIsDiskFull, mFreeSpace); + NS_DispatchToMainThread(runnable); +} + +void +GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd) +{ + struct fanotify_event_metadata* fem = nullptr; + char buf[4096]; + struct statfs sfs; + int32_t len, rc; + + do { + len = read(aFd, buf, sizeof(buf)); + } while(len == -1 && errno == EINTR); + + // Bail out if the file is busy. + if (len < 0 && errno == ETXTBSY) { + return; + } + + // We should get an exact multiple of fanotify_event_metadata + if (len <= 0 || (len % FAN_EVENT_METADATA_LEN != 0)) { + MOZ_CRASH("About to crash: fanotify_event_metadata read error."); + } + + fem = reinterpret_cast<fanotify_event_metadata *>(buf); + + while (FAN_EVENT_OK(fem, len)) { + rc = fstatfs(fem->fd, &sfs); + if (rc < 0) { + NS_WARNING("Unable to stat fan_notify fd"); + } else { + bool firstRun = mFreeSpace == UINT64_MAX; + mFreeSpace = sfs.f_bavail * sfs.f_bsize; + // We change from full <-> free depending on the free space and the + // low and high thresholds. + // Once we are in 'full' mode we send updates for all size changes with + // a minimum of time between messages or when we cross a size change + // threshold. + if (firstRun) { + mIsDiskFull = mFreeSpace <= mLowThreshold; + // Always notify the current state at first run. + NotifyUpdate(); + } else if (!mIsDiskFull && (mFreeSpace <= mLowThreshold)) { + mIsDiskFull = true; + NotifyUpdate(); + } else if (mIsDiskFull && (mFreeSpace > mHighThreshold)) { + mIsDiskFull = false; + NotifyUpdate(); + } else if (mIsDiskFull) { + if (mTimeout < TimeStamp::Now() - mLastTimestamp || + mSizeDelta < llabs(mFreeSpace - mLastFreeSpace)) { + NotifyUpdate(); + } + } + } + close(fem->fd); + fem = FAN_EVENT_NEXT(fem, len); + } +} + +void +StartDiskSpaceWatcher() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Bail out if called several times. + if (gHalDiskSpaceWatcher != nullptr) { + return; + } + + gHalDiskSpaceWatcher = new GonkDiskSpaceWatcher(); + + XRE_GetIOMessageLoop()->PostTask( + NewNonOwningRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStart)); +} + +void +StopDiskSpaceWatcher() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!gHalDiskSpaceWatcher) { + return; + } + + XRE_GetIOMessageLoop()->PostTask( + NewNonOwningRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStop)); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/gonk/GonkHal.cpp b/hal/gonk/GonkHal.cpp new file mode 100644 index 000000000..05d9295a2 --- /dev/null +++ b/hal/gonk/GonkHal.cpp @@ -0,0 +1,2045 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* Copyright 2012 Mozilla Foundation and Mozilla contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/android_alarm.h> +#include <math.h> +#include <regex.h> +#include <sched.h> +#include <stdio.h> +#include <sys/klog.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/resource.h> +#include <time.h> +#include <unistd.h> + +#include "mozilla/DebugOnly.h" + +#include "android/log.h" +#include "cutils/properties.h" +#include "hardware/hardware.h" +#include "hardware/lights.h" +#include "hardware_legacy/uevent.h" +#include "hardware_legacy/vibrator.h" +#include "hardware_legacy/power.h" +#include "libdisplay/GonkDisplay.h" +#include "utils/threads.h" + +#include "base/message_loop.h" +#include "base/task.h" + +#include "Hal.h" +#include "HalImpl.h" +#include "HalLog.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/battery/Constants.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FileUtils.h" +#include "mozilla/Monitor.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Services.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Preferences.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsAlgorithm.h" +#include "nsPrintfCString.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIRecoveryService.h" +#include "nsIRunnable.h" +#include "nsScreenManagerGonk.h" +#include "nsThreadUtils.h" +#include "nsThreadUtils.h" +#include "nsIThread.h" +#include "nsXULAppAPI.h" +#include "OrientationObserver.h" +#include "UeventPoller.h" +#include "nsIWritablePropertyBag2.h" +#include <algorithm> + +#define NsecPerMsec 1000000LL +#define NsecPerSec 1000000000 + +// The header linux/oom.h is not available in bionic libc. We +// redefine some of its constants here. + +#ifndef OOM_DISABLE +#define OOM_DISABLE (-17) +#endif + +#ifndef OOM_ADJUST_MIN +#define OOM_ADJUST_MIN (-16) +#endif + +#ifndef OOM_ADJUST_MAX +#define OOM_ADJUST_MAX 15 +#endif + +#ifndef OOM_SCORE_ADJ_MIN +#define OOM_SCORE_ADJ_MIN (-1000) +#endif + +#ifndef OOM_SCORE_ADJ_MAX +#define OOM_SCORE_ADJ_MAX 1000 +#endif + +#ifndef BATTERY_CHARGING_ARGB +#define BATTERY_CHARGING_ARGB 0x00FF0000 +#endif +#ifndef BATTERY_FULL_ARGB +#define BATTERY_FULL_ARGB 0x0000FF00 +#endif + +using namespace mozilla; +using namespace mozilla::hal; +using namespace mozilla::dom; + +namespace mozilla { +namespace hal_impl { + +/** + * These are defined by libhardware, specifically, hardware/libhardware/include/hardware/lights.h + * in the gonk subsystem. + * If these change and are exposed to JS, make sure nsIHal.idl is updated as well. + */ +enum LightType { + eHalLightID_Backlight = 0, + eHalLightID_Keyboard = 1, + eHalLightID_Buttons = 2, + eHalLightID_Battery = 3, + eHalLightID_Notifications = 4, + eHalLightID_Attention = 5, + eHalLightID_Bluetooth = 6, + eHalLightID_Wifi = 7, + eHalLightID_Count // This should stay at the end +}; +enum LightMode { + eHalLightMode_User = 0, // brightness is managed by user setting + eHalLightMode_Sensor = 1, // brightness is managed by a light sensor + eHalLightMode_Count +}; +enum FlashMode { + eHalLightFlash_None = 0, + eHalLightFlash_Timed = 1, // timed flashing. Use flashOnMS and flashOffMS for timing + eHalLightFlash_Hardware = 2, // hardware assisted flashing + eHalLightFlash_Count +}; + +struct LightConfiguration { + LightType light; + LightMode mode; + FlashMode flash; + uint32_t flashOnMS; + uint32_t flashOffMS; + uint32_t color; +}; + +static light_device_t* sLights[eHalLightID_Count]; // will be initialized to nullptr + +static light_device_t* +GetDevice(hw_module_t* module, char const* name) +{ + int err; + hw_device_t* device; + err = module->methods->open(module, name, &device); + if (err == 0) { + return (light_device_t*)device; + } else { + return nullptr; + } +} + +static void +InitLights() +{ + // assume that if backlight is nullptr, nothing has been set yet + // if this is not true, the initialization will occur everytime a light is read or set! + if (!sLights[eHalLightID_Backlight]) { + int err; + hw_module_t* module; + + err = hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module); + if (err == 0) { + sLights[eHalLightID_Backlight] + = GetDevice(module, LIGHT_ID_BACKLIGHT); + sLights[eHalLightID_Keyboard] + = GetDevice(module, LIGHT_ID_KEYBOARD); + sLights[eHalLightID_Buttons] + = GetDevice(module, LIGHT_ID_BUTTONS); + sLights[eHalLightID_Battery] + = GetDevice(module, LIGHT_ID_BATTERY); + sLights[eHalLightID_Notifications] + = GetDevice(module, LIGHT_ID_NOTIFICATIONS); + sLights[eHalLightID_Attention] + = GetDevice(module, LIGHT_ID_ATTENTION); + sLights[eHalLightID_Bluetooth] + = GetDevice(module, LIGHT_ID_BLUETOOTH); + sLights[eHalLightID_Wifi] + = GetDevice(module, LIGHT_ID_WIFI); + } + } +} + +/** + * The state last set for the lights until liblights supports + * getting the light state. + */ +static light_state_t sStoredLightState[eHalLightID_Count]; + +/** +* Set the value of a light to a particular color, with a specific flash pattern. +* light specifices which light. See Hal.idl for the list of constants +* mode specifies user set or based on ambient light sensor +* flash specifies whether or how to flash the light +* flashOnMS and flashOffMS specify the pattern for XXX flash mode +* color specifies the color. If the light doesn't support color, the given color is +* transformed into a brightness, or just an on/off if that is all the light is capable of. +* returns true if successful and false if failed. +*/ +static bool +SetLight(LightType light, const LightConfiguration& aConfig) +{ + light_state_t state; + + InitLights(); + + if (light < 0 || light >= eHalLightID_Count || + sLights[light] == nullptr) { + return false; + } + + memset(&state, 0, sizeof(light_state_t)); + state.color = aConfig.color; + state.flashMode = aConfig.flash; + state.flashOnMS = aConfig.flashOnMS; + state.flashOffMS = aConfig.flashOffMS; + state.brightnessMode = aConfig.mode; + + sLights[light]->set_light(sLights[light], &state); + sStoredLightState[light] = state; + return true; +} + +/** +* GET the value of a light returning a particular color, with a specific flash pattern. +* returns true if successful and false if failed. +*/ +static bool +GetLight(LightType light, LightConfiguration* aConfig) +{ + light_state_t state; + + if (light < 0 || light >= eHalLightID_Count || + sLights[light] == nullptr) { + return false; + } + + memset(&state, 0, sizeof(light_state_t)); + state = sStoredLightState[light]; + + aConfig->light = light; + aConfig->color = state.color; + aConfig->flash = FlashMode(state.flashMode); + aConfig->flashOnMS = state.flashOnMS; + aConfig->flashOffMS = state.flashOffMS; + aConfig->mode = LightMode(state.brightnessMode); + + return true; +} + +namespace { + +/** + * This runnable runs for the lifetime of the program, once started. It's + * responsible for "playing" vibration patterns. + */ +class VibratorRunnable final + : public nsIRunnable + , public nsIObserver +{ +public: + VibratorRunnable() + : mMonitor("VibratorRunnable") + , mIndex(0) + { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (!os) { + NS_WARNING("Could not get observer service!"); + return; + } + + os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE + NS_DECL_NSIOBSERVER + + // Run on the main thread, not the vibrator thread. + void Vibrate(const nsTArray<uint32_t> &pattern); + void CancelVibrate(); + + static bool ShuttingDown() { return sShuttingDown; } + +protected: + ~VibratorRunnable() {} + +private: + Monitor mMonitor; + + // The currently-playing pattern. + nsTArray<uint32_t> mPattern; + + // The index we're at in the currently-playing pattern. If mIndex >= + // mPattern.Length(), then we're not currently playing anything. + uint32_t mIndex; + + // Set to true in our shutdown observer. When this is true, we kill the + // vibrator thread. + static bool sShuttingDown; +}; + +NS_IMPL_ISUPPORTS(VibratorRunnable, nsIRunnable, nsIObserver); + +bool VibratorRunnable::sShuttingDown = false; + +static StaticRefPtr<VibratorRunnable> sVibratorRunnable; + +NS_IMETHODIMP +VibratorRunnable::Run() +{ + MonitorAutoLock lock(mMonitor); + + // We currently assume that mMonitor.Wait(X) waits for X milliseconds. But in + // reality, the kernel might not switch to this thread for some time after the + // wait expires. So there's potential for some inaccuracy here. + // + // This doesn't worry me too much. Note that we don't even start vibrating + // immediately when VibratorRunnable::Vibrate is called -- we go through a + // condvar onto another thread. Better just to be chill about small errors in + // the timing here. + + while (!sShuttingDown) { + if (mIndex < mPattern.Length()) { + uint32_t duration = mPattern[mIndex]; + if (mIndex % 2 == 0) { + vibrator_on(duration); + } + mIndex++; + mMonitor.Wait(PR_MillisecondsToInterval(duration)); + } + else { + mMonitor.Wait(); + } + } + sVibratorRunnable = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +VibratorRunnable::Observe(nsISupports *subject, const char *topic, + const char16_t *data) +{ + MOZ_ASSERT(strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0); + MonitorAutoLock lock(mMonitor); + sShuttingDown = true; + mMonitor.Notify(); + + return NS_OK; +} + +void +VibratorRunnable::Vibrate(const nsTArray<uint32_t> &pattern) +{ + MonitorAutoLock lock(mMonitor); + mPattern = pattern; + mIndex = 0; + mMonitor.Notify(); +} + +void +VibratorRunnable::CancelVibrate() +{ + MonitorAutoLock lock(mMonitor); + mPattern.Clear(); + mPattern.AppendElement(0); + mIndex = 0; + mMonitor.Notify(); +} + +void +EnsureVibratorThreadInitialized() +{ + if (sVibratorRunnable) { + return; + } + + sVibratorRunnable = new VibratorRunnable(); + nsCOMPtr<nsIThread> thread; + NS_NewThread(getter_AddRefs(thread), sVibratorRunnable); +} + +} // namespace + +void +Vibrate(const nsTArray<uint32_t> &pattern, const hal::WindowIdentifier &) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (VibratorRunnable::ShuttingDown()) { + return; + } + EnsureVibratorThreadInitialized(); + sVibratorRunnable->Vibrate(pattern); +} + +void +CancelVibrate(const hal::WindowIdentifier &) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (VibratorRunnable::ShuttingDown()) { + return; + } + EnsureVibratorThreadInitialized(); + sVibratorRunnable->CancelVibrate(); +} + +namespace { + +class BatteryUpdater : public Runnable { +public: + NS_IMETHOD Run() override + { + hal::BatteryInformation info; + hal_impl::GetCurrentBatteryInformation(&info); + + // Control the battery indicator (led light) here using BatteryInformation + // we just retrieved. + uint32_t color = 0; // Format: 0x00rrggbb. + if (info.charging() && (info.level() == 1)) { + // Charging and battery full. + color = BATTERY_FULL_ARGB; + } else if (info.charging() && (info.level() < 1)) { + // Charging but not full. + color = BATTERY_CHARGING_ARGB; + } // else turn off battery indicator. + + LightConfiguration aConfig; + aConfig.light = eHalLightID_Battery; + aConfig.mode = eHalLightMode_User; + aConfig.flash = eHalLightFlash_None; + aConfig.flashOnMS = aConfig.flashOffMS = 0; + aConfig.color = color; + + SetLight(eHalLightID_Battery, aConfig); + + hal::NotifyBatteryChange(info); + + { + // bug 975667 + // Gecko gonk hal is required to emit battery charging/level notification via nsIObserverService. + // This is useful for XPCOM components that are not statically linked to Gecko and cannot call + // hal::EnableBatteryNotifications + nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService(); + nsCOMPtr<nsIWritablePropertyBag2> propbag = + do_CreateInstance("@mozilla.org/hash-property-bag;1"); + if (obsService && propbag) { + propbag->SetPropertyAsBool(NS_LITERAL_STRING("charging"), + info.charging()); + propbag->SetPropertyAsDouble(NS_LITERAL_STRING("level"), + info.level()); + + obsService->NotifyObservers(propbag, "gonkhal-battery-notifier", nullptr); + } + } + + return NS_OK; + } +}; + +} // namespace + +class BatteryObserver final : public IUeventObserver +{ +public: + NS_INLINE_DECL_REFCOUNTING(BatteryObserver) + + BatteryObserver() + :mUpdater(new BatteryUpdater()) + { + } + + virtual void Notify(const NetlinkEvent &aEvent) + { + // this will run on IO thread + NetlinkEvent *event = const_cast<NetlinkEvent*>(&aEvent); + const char *subsystem = event->getSubsystem(); + // e.g. DEVPATH=/devices/platform/sec-battery/power_supply/battery + const char *devpath = event->findParam("DEVPATH"); + if (strcmp(subsystem, "power_supply") == 0 && + strstr(devpath, "battery")) { + // aEvent will be valid only in this method. + NS_DispatchToMainThread(mUpdater); + } + } + +protected: + ~BatteryObserver() {} + +private: + RefPtr<BatteryUpdater> mUpdater; +}; + +// sBatteryObserver is owned by the IO thread. Only the IO thread may +// create or destroy it. +static StaticRefPtr<BatteryObserver> sBatteryObserver; + +static void +RegisterBatteryObserverIOThread() +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + MOZ_ASSERT(!sBatteryObserver); + + sBatteryObserver = new BatteryObserver(); + RegisterUeventListener(sBatteryObserver); +} + +void +EnableBatteryNotifications() +{ + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(RegisterBatteryObserverIOThread)); +} + +static void +UnregisterBatteryObserverIOThread() +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + MOZ_ASSERT(sBatteryObserver); + + UnregisterUeventListener(sBatteryObserver); + sBatteryObserver = nullptr; +} + +void +DisableBatteryNotifications() +{ + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(UnregisterBatteryObserverIOThread)); +} + +static bool +GetCurrentBatteryCharge(int* aCharge) +{ + bool success = ReadSysFile("/sys/class/power_supply/battery/capacity", + aCharge); + if (!success) { + return false; + } + + #ifdef DEBUG + if ((*aCharge < 0) || (*aCharge > 100)) { + HAL_LOG("charge level contains unknown value: %d", *aCharge); + } + #endif + + return (*aCharge >= 0) && (*aCharge <= 100); +} + +static bool +GetCurrentBatteryCharging(int* aCharging) +{ + static const DebugOnly<int> BATTERY_NOT_CHARGING = 0; + static const int BATTERY_CHARGING_USB = 1; + static const int BATTERY_CHARGING_AC = 2; + + // Generic device support + + int chargingSrc; + bool success = + ReadSysFile("/sys/class/power_supply/battery/charging_source", &chargingSrc); + + if (success) { + #ifdef DEBUG + if (chargingSrc != BATTERY_NOT_CHARGING && + chargingSrc != BATTERY_CHARGING_USB && + chargingSrc != BATTERY_CHARGING_AC) { + HAL_LOG("charging_source contained unknown value: %d", chargingSrc); + } + #endif + + *aCharging = (chargingSrc == BATTERY_CHARGING_USB || + chargingSrc == BATTERY_CHARGING_AC); + return true; + } + + // Otoro device support + + char chargingSrcString[16]; + + success = ReadSysFile("/sys/class/power_supply/battery/status", + chargingSrcString, sizeof(chargingSrcString)); + if (success) { + *aCharging = strcmp(chargingSrcString, "Charging") == 0 || + strcmp(chargingSrcString, "Full") == 0; + return true; + } + + return false; +} + +void +GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) +{ + int charge; + static bool previousCharging = false; + static double previousLevel = 0.0, remainingTime = 0.0; + static struct timespec lastLevelChange; + struct timespec now; + double dtime, dlevel; + + if (GetCurrentBatteryCharge(&charge)) { + aBatteryInfo->level() = (double)charge / 100.0; + } else { + aBatteryInfo->level() = dom::battery::kDefaultLevel; + } + + int charging; + + if (GetCurrentBatteryCharging(&charging)) { + aBatteryInfo->charging() = charging; + } else { + aBatteryInfo->charging() = true; + } + + if (aBatteryInfo->charging() != previousCharging){ + aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime; + memset(&lastLevelChange, 0, sizeof(struct timespec)); + remainingTime = 0.0; + } + + if (aBatteryInfo->charging()) { + if (aBatteryInfo->level() == 1.0) { + aBatteryInfo->remainingTime() = dom::battery::kDefaultRemainingTime; + } else if (aBatteryInfo->level() != previousLevel){ + if (lastLevelChange.tv_sec != 0) { + clock_gettime(CLOCK_MONOTONIC, &now); + dtime = now.tv_sec - lastLevelChange.tv_sec; + dlevel = aBatteryInfo->level() - previousLevel; + + if (dlevel <= 0.0) { + aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime; + } else { + remainingTime = (double) round(dtime / dlevel * (1.0 - aBatteryInfo->level())); + aBatteryInfo->remainingTime() = remainingTime; + } + + lastLevelChange = now; + } else { // lastLevelChange.tv_sec == 0 + clock_gettime(CLOCK_MONOTONIC, &lastLevelChange); + aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime; + } + + } else { + clock_gettime(CLOCK_MONOTONIC, &now); + dtime = now.tv_sec - lastLevelChange.tv_sec; + if (dtime < remainingTime) { + aBatteryInfo->remainingTime() = round(remainingTime - dtime); + } else { + aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime; + } + + } + + } else { + if (aBatteryInfo->level() == 0.0) { + aBatteryInfo->remainingTime() = dom::battery::kDefaultRemainingTime; + } else if (aBatteryInfo->level() != previousLevel){ + if (lastLevelChange.tv_sec != 0) { + clock_gettime(CLOCK_MONOTONIC, &now); + dtime = now.tv_sec - lastLevelChange.tv_sec; + dlevel = previousLevel - aBatteryInfo->level(); + + if (dlevel <= 0.0) { + aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime; + } else { + remainingTime = (double) round(dtime / dlevel * aBatteryInfo->level()); + aBatteryInfo->remainingTime() = remainingTime; + } + + lastLevelChange = now; + } else { // lastLevelChange.tv_sec == 0 + clock_gettime(CLOCK_MONOTONIC, &lastLevelChange); + aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime; + } + + } else { + clock_gettime(CLOCK_MONOTONIC, &now); + dtime = now.tv_sec - lastLevelChange.tv_sec; + if (dtime < remainingTime) { + aBatteryInfo->remainingTime() = round(remainingTime - dtime); + } else { + aBatteryInfo->remainingTime() = dom::battery::kUnknownRemainingTime; + } + + } + } + + previousCharging = aBatteryInfo->charging(); + previousLevel = aBatteryInfo->level(); +} + +namespace { + +// We can write to screenEnabledFilename to enable/disable the screen, but when +// we read, we always get "mem"! So we have to keep track ourselves whether +// the screen is on or not. +bool sScreenEnabled = true; + +// We can read wakeLockFilename to find out whether the cpu wake lock +// is already acquired, but reading and parsing it is a lot more work +// than tracking it ourselves, and it won't be accurate anyway (kernel +// internal wake locks aren't counted here.) +bool sCpuSleepAllowed = true; + +// Some CPU wake locks may be acquired internally in HAL. We use a counter to +// keep track of these needs. Note we have to hold |sInternalLockCpuMutex| +// when reading or writing this variable to ensure thread-safe. +int32_t sInternalLockCpuCount = 0; + +} // namespace + +bool +GetScreenEnabled() +{ + return sScreenEnabled; +} + +void +SetScreenEnabled(bool aEnabled) +{ + GetGonkDisplay()->SetEnabled(aEnabled); + sScreenEnabled = aEnabled; +} + +bool +GetKeyLightEnabled() +{ + LightConfiguration config; + bool ok = GetLight(eHalLightID_Buttons, &config); + if (ok) { + return (config.color != 0x00000000); + } + return false; +} + +void +SetKeyLightEnabled(bool aEnabled) +{ + LightConfiguration config; + config.mode = eHalLightMode_User; + config.flash = eHalLightFlash_None; + config.flashOnMS = config.flashOffMS = 0; + config.color = 0x00000000; + + if (aEnabled) { + // Convert the value in [0, 1] to an int between 0 and 255 and then convert + // it to a color. Note that the high byte is FF, corresponding to the alpha + // channel. + double brightness = GetScreenBrightness(); + uint32_t val = static_cast<int>(round(brightness * 255.0)); + uint32_t color = (0xff<<24) + (val<<16) + (val<<8) + val; + + config.color = color; + } + + SetLight(eHalLightID_Buttons, config); + SetLight(eHalLightID_Keyboard, config); +} + +double +GetScreenBrightness() +{ + LightConfiguration config; + LightType light = eHalLightID_Backlight; + + bool ok = GetLight(light, &config); + if (ok) { + // backlight is brightness only, so using one of the RGB elements as value. + int brightness = config.color & 0xFF; + return brightness / 255.0; + } + // If GetLight fails, it's because the light doesn't exist. So return + // a value corresponding to "off". + return 0; +} + +void +SetScreenBrightness(double brightness) +{ + // Don't use De Morgan's law to push the ! into this expression; we want to + // catch NaN too. + if (!(0 <= brightness && brightness <= 1)) { + HAL_LOG("SetScreenBrightness: Dropping illegal brightness %f.", brightness); + return; + } + + // Convert the value in [0, 1] to an int between 0 and 255 and convert to a color + // note that the high byte is FF, corresponding to the alpha channel. + uint32_t val = static_cast<int>(round(brightness * 255.0)); + uint32_t color = (0xff<<24) + (val<<16) + (val<<8) + val; + + LightConfiguration config; + config.mode = eHalLightMode_User; + config.flash = eHalLightFlash_None; + config.flashOnMS = config.flashOffMS = 0; + config.color = color; + SetLight(eHalLightID_Backlight, config); + if (GetKeyLightEnabled()) { + SetLight(eHalLightID_Buttons, config); + SetLight(eHalLightID_Keyboard, config); + } +} + +static StaticMutex sInternalLockCpuMutex; + +static void +UpdateCpuSleepState() +{ + const char *wakeLockFilename = "/sys/power/wake_lock"; + const char *wakeUnlockFilename = "/sys/power/wake_unlock"; + + sInternalLockCpuMutex.AssertCurrentThreadOwns(); + bool allowed = sCpuSleepAllowed && !sInternalLockCpuCount; + WriteSysFile(allowed ? wakeUnlockFilename : wakeLockFilename, "gecko"); +} + +static void +InternalLockCpu() { + StaticMutexAutoLock lock(sInternalLockCpuMutex); + ++sInternalLockCpuCount; + UpdateCpuSleepState(); +} + +static void +InternalUnlockCpu() { + StaticMutexAutoLock lock(sInternalLockCpuMutex); + --sInternalLockCpuCount; + UpdateCpuSleepState(); +} + +bool +GetCpuSleepAllowed() +{ + return sCpuSleepAllowed; +} + +void +SetCpuSleepAllowed(bool aAllowed) +{ + StaticMutexAutoLock lock(sInternalLockCpuMutex); + sCpuSleepAllowed = aAllowed; + UpdateCpuSleepState(); +} + +void +AdjustSystemClock(int64_t aDeltaMilliseconds) +{ + int fd; + struct timespec now; + + if (aDeltaMilliseconds == 0) { + return; + } + + // Preventing context switch before setting system clock + sched_yield(); + clock_gettime(CLOCK_REALTIME, &now); + now.tv_sec += (time_t)(aDeltaMilliseconds / 1000LL); + now.tv_nsec += (long)((aDeltaMilliseconds % 1000LL) * NsecPerMsec); + if (now.tv_nsec >= NsecPerSec) { + now.tv_sec += 1; + now.tv_nsec -= NsecPerSec; + } + + if (now.tv_nsec < 0) { + now.tv_nsec += NsecPerSec; + now.tv_sec -= 1; + } + + do { + fd = open("/dev/alarm", O_RDWR); + } while (fd == -1 && errno == EINTR); + ScopedClose autoClose(fd); + if (fd < 0) { + HAL_LOG("Failed to open /dev/alarm: %s", strerror(errno)); + return; + } + + if (ioctl(fd, ANDROID_ALARM_SET_RTC, &now) < 0) { + HAL_LOG("ANDROID_ALARM_SET_RTC failed: %s", strerror(errno)); + } + + hal::NotifySystemClockChange(aDeltaMilliseconds); +} + +int32_t +GetTimezoneOffset() +{ + PRExplodedTime prTime; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &prTime); + + // Daylight saving time (DST) will be taken into account. + int32_t offset = prTime.tm_params.tp_gmt_offset; + offset += prTime.tm_params.tp_dst_offset; + + // Returns the timezone offset relative to UTC in minutes. + return -(offset / 60); +} + +static int32_t sKernelTimezoneOffset = 0; + +static void +UpdateKernelTimezone(int32_t timezoneOffset) +{ + if (sKernelTimezoneOffset == timezoneOffset) { + return; + } + + // Tell the kernel about the new time zone as well, so that FAT filesystems + // will get local timestamps rather than UTC timestamps. + // + // We assume that /init.rc has a sysclktz entry so that settimeofday has + // already been called once before we call it (there is a side-effect in + // the kernel the very first time settimeofday is called where it does some + // special processing if you only set the timezone). + struct timezone tz; + memset(&tz, 0, sizeof(tz)); + tz.tz_minuteswest = timezoneOffset; + settimeofday(nullptr, &tz); + sKernelTimezoneOffset = timezoneOffset; +} + +void +SetTimezone(const nsCString& aTimezoneSpec) +{ + if (aTimezoneSpec.Equals(GetTimezone())) { + // Even though the timezone hasn't changed, we still need to tell the + // kernel what the current timezone is. The timezone is persisted in + // a property and doesn't change across reboots, but the kernel still + // needs to be updated on every boot. + UpdateKernelTimezone(GetTimezoneOffset()); + return; + } + + int32_t oldTimezoneOffsetMinutes = GetTimezoneOffset(); + property_set("persist.sys.timezone", aTimezoneSpec.get()); + // This function is automatically called by the other time conversion + // functions that depend on the timezone. To be safe, we call it manually. + tzset(); + int32_t newTimezoneOffsetMinutes = GetTimezoneOffset(); + UpdateKernelTimezone(newTimezoneOffsetMinutes); + hal::NotifySystemTimezoneChange( + hal::SystemTimezoneChangeInformation( + oldTimezoneOffsetMinutes, newTimezoneOffsetMinutes)); +} + +nsCString +GetTimezone() +{ + char timezone[32]; + property_get("persist.sys.timezone", timezone, ""); + return nsCString(timezone); +} + +void +EnableSystemClockChangeNotifications() +{ +} + +void +DisableSystemClockChangeNotifications() +{ +} + +void +EnableSystemTimezoneChangeNotifications() +{ +} + +void +DisableSystemTimezoneChangeNotifications() +{ +} + +// Nothing to do here. Gonk widgetry always listens for screen +// orientation changes. +void +EnableScreenConfigurationNotifications() +{ +} + +void +DisableScreenConfigurationNotifications() +{ +} + +void +GetCurrentScreenConfiguration(hal::ScreenConfiguration* aScreenConfiguration) +{ + RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen(); + *aScreenConfiguration = screen->GetConfiguration(); +} + +bool +LockScreenOrientation(const dom::ScreenOrientationInternal& aOrientation) +{ + return OrientationObserver::GetInstance()->LockScreenOrientation(aOrientation); +} + +void +UnlockScreenOrientation() +{ + OrientationObserver::GetInstance()->UnlockScreenOrientation(); +} + +// This thread will wait for the alarm firing by a blocking IO. +static pthread_t sAlarmFireWatcherThread; + +// If |sAlarmData| is non-null, it's owned by the alarm-watcher thread. +struct AlarmData { +public: + AlarmData(int aFd) : mFd(aFd), + mGeneration(sNextGeneration++), + mShuttingDown(false) {} + ScopedClose mFd; + int mGeneration; + bool mShuttingDown; + + static int sNextGeneration; + +}; + +int AlarmData::sNextGeneration = 0; + +AlarmData* sAlarmData = nullptr; + +class AlarmFiredEvent : public Runnable { +public: + AlarmFiredEvent(int aGeneration) : mGeneration(aGeneration) {} + + NS_IMETHOD Run() override { + // Guard against spurious notifications caused by an alarm firing + // concurrently with it being disabled. + if (sAlarmData && !sAlarmData->mShuttingDown && + mGeneration == sAlarmData->mGeneration) { + hal::NotifyAlarmFired(); + } + // The fired alarm event has been delivered to the observer (if needed); + // we can now release a CPU wake lock. + InternalUnlockCpu(); + return NS_OK; + } + +private: + int mGeneration; +}; + +// Runs on alarm-watcher thread. +static void +DestroyAlarmData(void* aData) +{ + AlarmData* alarmData = static_cast<AlarmData*>(aData); + delete alarmData; +} + +// Runs on alarm-watcher thread. +void ShutDownAlarm(int aSigno) +{ + if (aSigno == SIGUSR1 && sAlarmData) { + sAlarmData->mShuttingDown = true; + } + return; +} + +static void* +WaitForAlarm(void* aData) +{ + pthread_cleanup_push(DestroyAlarmData, aData); + + AlarmData* alarmData = static_cast<AlarmData*>(aData); + + while (!alarmData->mShuttingDown) { + int alarmTypeFlags = 0; + + // ALARM_WAIT apparently will block even if an alarm hasn't been + // programmed, although this behavior doesn't seem to be + // documented. We rely on that here to avoid spinning the CPU + // while awaiting an alarm to be programmed. + do { + alarmTypeFlags = ioctl(alarmData->mFd, ANDROID_ALARM_WAIT); + } while (alarmTypeFlags < 0 && errno == EINTR && + !alarmData->mShuttingDown); + + if (!alarmData->mShuttingDown && alarmTypeFlags >= 0 && + (alarmTypeFlags & ANDROID_ALARM_RTC_WAKEUP_MASK)) { + // To make sure the observer can get the alarm firing notification + // *on time* (the system won't sleep during the process in any way), + // we need to acquire a CPU wake lock before firing the alarm event. + InternalLockCpu(); + RefPtr<AlarmFiredEvent> event = + new AlarmFiredEvent(alarmData->mGeneration); + NS_DispatchToMainThread(event); + } + } + + pthread_cleanup_pop(1); + return nullptr; +} + +bool +EnableAlarm() +{ + MOZ_ASSERT(!sAlarmData); + + int alarmFd = open("/dev/alarm", O_RDWR); + if (alarmFd < 0) { + HAL_LOG("Failed to open alarm device: %s.", strerror(errno)); + return false; + } + + UniquePtr<AlarmData> alarmData = MakeUnique<AlarmData>(alarmFd); + + struct sigaction actions; + memset(&actions, 0, sizeof(actions)); + sigemptyset(&actions.sa_mask); + actions.sa_flags = 0; + actions.sa_handler = ShutDownAlarm; + if (sigaction(SIGUSR1, &actions, nullptr)) { + HAL_LOG("Failed to set SIGUSR1 signal for alarm-watcher thread."); + return false; + } + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + int status = pthread_create(&sAlarmFireWatcherThread, &attr, WaitForAlarm, + alarmData.get()); + if (status) { + alarmData.reset(); + HAL_LOG("Failed to create alarm-watcher thread. Status: %d.", status); + return false; + } + + pthread_attr_destroy(&attr); + + // The thread owns this now. We only hold a pointer. + sAlarmData = alarmData.release(); + return true; +} + +void +DisableAlarm() +{ + MOZ_ASSERT(sAlarmData); + + // NB: this must happen-before the thread cancellation. + sAlarmData = nullptr; + + // The cancel will interrupt the thread and destroy it, freeing the + // data pointed at by sAlarmData. + DebugOnly<int> err = pthread_kill(sAlarmFireWatcherThread, SIGUSR1); + MOZ_ASSERT(!err); +} + +bool +SetAlarm(int32_t aSeconds, int32_t aNanoseconds) +{ + if (!sAlarmData) { + HAL_LOG("We should have enabled the alarm."); + return false; + } + + struct timespec ts; + ts.tv_sec = aSeconds; + ts.tv_nsec = aNanoseconds; + + // Currently we only support RTC wakeup alarm type. + const int result = ioctl(sAlarmData->mFd, + ANDROID_ALARM_SET(ANDROID_ALARM_RTC_WAKEUP), &ts); + + if (result < 0) { + HAL_LOG("Unable to set alarm: %s.", strerror(errno)); + return false; + } + + return true; +} + +static int +OomAdjOfOomScoreAdj(int aOomScoreAdj) +{ + // Convert OOM adjustment from the domain of /proc/<pid>/oom_score_adj + // to the domain of /proc/<pid>/oom_adj. + + int adj; + + if (aOomScoreAdj < 0) { + adj = (OOM_DISABLE * aOomScoreAdj) / OOM_SCORE_ADJ_MIN; + } else { + adj = (OOM_ADJUST_MAX * aOomScoreAdj) / OOM_SCORE_ADJ_MAX; + } + + return adj; +} + +static void +RoundOomScoreAdjUpWithLRU(int& aOomScoreAdj, uint32_t aLRU) +{ + // We want to add minimum value to round OomScoreAdj up according to + // the steps by aLRU. + aOomScoreAdj += + ceil(((float)OOM_SCORE_ADJ_MAX / OOM_ADJUST_MAX) * aLRU); +} + +#define OOM_LOG(level, args...) __android_log_print(level, "OomLogger", ##args) +class OomVictimLogger final + : public nsIObserver +{ +public: + OomVictimLogger() + : mLastLineChecked(-1.0), + mRegexes(nullptr) + { + // Enable timestamps in kernel's printk + WriteSysFile("/sys/module/printk/parameters/time", "Y"); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + +protected: + ~OomVictimLogger() {} + +private: + double mLastLineChecked; + UniqueFreePtr<regex_t> mRegexes; +}; +NS_IMPL_ISUPPORTS(OomVictimLogger, nsIObserver); + +NS_IMETHODIMP +OomVictimLogger::Observe( + nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + nsDependentCString event_type(aTopic); + if (!event_type.EqualsLiteral("ipc:content-shutdown")) { + return NS_OK; + } + + // OOM message finding regexes + const char* const regexes_raw[] = { + ".*select.*to kill.*", + ".*send sigkill to.*", + ".*lowmem_shrink.*", + ".*[Oo]ut of [Mm]emory.*", + ".*[Kk]ill [Pp]rocess.*", + ".*[Kk]illed [Pp]rocess.*", + ".*oom-killer.*", + // The regexes below are for the output of dump_task from oom_kill.c + // 1st - title 2nd - body lines (8 ints and a string) + // oom_adj and oom_score_adj can be negative + "\\[ pid \\] uid tgid total_vm rss cpu oom_adj oom_score_adj name", + "\\[.*[0-9][0-9]*\\][ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*[0-9][0-9]*[ ]*.[0-9][0-9]*[ ]*.[0-9][0-9]*.*" + }; + const size_t regex_count = ArrayLength(regexes_raw); + + // Compile our regex just in time + if (!mRegexes) { + UniqueFreePtr<regex_t> regexes( + static_cast<regex_t*>(malloc(sizeof(regex_t) * regex_count)) + ); + mRegexes.swap(regexes); + for (size_t i = 0; i < regex_count; i++) { + int compilation_err = + regcomp(&(mRegexes.get()[i]), regexes_raw[i], REG_NOSUB); + if (compilation_err) { + OOM_LOG(ANDROID_LOG_ERROR, "Cannot compile regex \"%s\"\n", regexes_raw[i]); + return NS_OK; + } + } + } + +#ifndef KLOG_SIZE_BUFFER + // Upstream bionic in commit + // e249b059637b49a285ed9f58a2a18bfd054e5d95 + // deprecated the old klog defs. + // Our current bionic does not hit this + // change yet so handle the future change. + // (ICS doesn't have KLOG_SIZE_BUFFER but + // JB and onwards does.) + #define KLOG_SIZE_BUFFER KLOG_WRITE +#endif + // Retreive kernel log + int msg_buf_size = klogctl(KLOG_SIZE_BUFFER, NULL, 0); + UniqueFreePtr<char> msg_buf(static_cast<char *>(malloc(msg_buf_size + 1))); + int read_size = klogctl(KLOG_READ_ALL, msg_buf.get(), msg_buf_size); + + // Turn buffer into cstring + read_size = read_size > msg_buf_size ? msg_buf_size : read_size; + msg_buf.get()[read_size] = '\0'; + + // Foreach line + char* line_end; + char* line_begin = msg_buf.get(); + for (; (line_end = strchr(line_begin, '\n')); line_begin = line_end + 1) { + // make line into cstring + *line_end = '\0'; + + // Note: Kernel messages look like: + // <5>[63648.286409] sd 35:0:0:0: Attached scsi generic sg1 type 0 + // 5 is the loging level + // [*] is the time timestamp, seconds since boot + // last comes the logged message + + // Since the logging level can be a string we must + // skip it since scanf lacks wildcard matching + char* timestamp_begin = strchr(line_begin, '['); + char after_float; + double lineTimestamp = -1; + bool lineTimestampFound = false; + if (timestamp_begin && + // Note: scanf treats a ' ' as [ ]* + // Note: scanf treats [ %lf] as [ %lf thus we must check + // for the closing bracket outselves. + 2 == sscanf(timestamp_begin, "[ %lf%c", &lineTimestamp, &after_float) && + after_float == ']') { + if (lineTimestamp <= mLastLineChecked) { + continue; + } + + lineTimestampFound = true; + mLastLineChecked = lineTimestamp; + } + + // Log interesting lines + for (size_t i = 0; i < regex_count; i++) { + int matching = !regexec(&(mRegexes.get()[i]), line_begin, 0, NULL, 0); + if (matching) { + // Log content of kernel message. We try to skip the ], but if for + // some reason (most likely due to buffer overflow/wraparound), we + // can't find the ] then we just log the entire line. + char* endOfTimestamp = strchr(line_begin, ']'); + if (endOfTimestamp && endOfTimestamp[1] == ' ') { + // skip the ] and the space that follows it + line_begin = endOfTimestamp + 2; + } + if (!lineTimestampFound) { + OOM_LOG(ANDROID_LOG_WARN, "following kill message may be a duplicate"); + } + OOM_LOG(ANDROID_LOG_ERROR, "[Kill]: %s\n", line_begin); + break; + } + } + } + + return NS_OK; +} + +/** + * Wraps a particular ProcessPriority, giving us easy access to the prefs that + * are relevant to it. + * + * Creating a PriorityClass also ensures that the control group is created. + */ +class PriorityClass +{ +public: + /** + * Create a PriorityClass for the given ProcessPriority. This implicitly + * reads the relevant prefs and opens the cgroup.procs file of the relevant + * control group caching its file descriptor for later use. + */ + PriorityClass(ProcessPriority aPriority); + + /** + * Closes the file descriptor for the cgroup.procs file of the associated + * control group. + */ + ~PriorityClass(); + + PriorityClass(const PriorityClass& aOther); + PriorityClass& operator=(const PriorityClass& aOther); + + ProcessPriority Priority() + { + return mPriority; + } + + int32_t OomScoreAdj() + { + return clamped<int32_t>(mOomScoreAdj, OOM_SCORE_ADJ_MIN, OOM_SCORE_ADJ_MAX); + } + + int32_t KillUnderKB() + { + return mKillUnderKB; + } + + nsCString CGroup() + { + return mGroup; + } + + /** + * Adds a process to this priority class, this moves the process' PID into + * the associated control group. + * + * @param aPid The PID of the process to be added. + */ + void AddProcess(int aPid); + +private: + ProcessPriority mPriority; + int32_t mOomScoreAdj; + int32_t mKillUnderKB; + int mCpuCGroupProcsFd; + int mMemCGroupProcsFd; + nsCString mGroup; + + /** + * Return a string that identifies where we can find the value of aPref + * that's specific to mPriority. For example, we might return + * "hal.processPriorityManager.gonk.FOREGROUND_HIGH.oomScoreAdjust". + */ + nsCString PriorityPrefName(const char* aPref) + { + return nsPrintfCString("hal.processPriorityManager.gonk.%s.%s", + ProcessPriorityToString(mPriority), aPref); + } + + /** + * Get the full path of the cgroup.procs file associated with the group. + */ + nsCString CpuCGroupProcsFilename() + { + nsCString cgroupName = mGroup; + + /* If mGroup is empty, our cgroup.procs file is the root procs file, + * located at /dev/cpuctl/cgroup.procs. Otherwise our procs file is + * /dev/cpuctl/NAME/cgroup.procs. */ + + if (!mGroup.IsEmpty()) { + cgroupName.AppendLiteral("/"); + } + + return NS_LITERAL_CSTRING("/dev/cpuctl/") + cgroupName + + NS_LITERAL_CSTRING("cgroup.procs"); + } + + nsCString MemCGroupProcsFilename() + { + nsCString cgroupName = mGroup; + + /* If mGroup is empty, our cgroup.procs file is the root procs file, + * located at /sys/fs/cgroup/memory/cgroup.procs. Otherwise our procs + * file is /sys/fs/cgroup/memory/NAME/cgroup.procs. */ + + if (!mGroup.IsEmpty()) { + cgroupName.AppendLiteral("/"); + } + + return NS_LITERAL_CSTRING("/sys/fs/cgroup/memory/") + cgroupName + + NS_LITERAL_CSTRING("cgroup.procs"); + } + + int OpenCpuCGroupProcs() + { + return open(CpuCGroupProcsFilename().get(), O_WRONLY); + } + + int OpenMemCGroupProcs() + { + return open(MemCGroupProcsFilename().get(), O_WRONLY); + } +}; + +/** + * Try to create the cgroup for the given PriorityClass, if it doesn't already + * exist. This essentially implements mkdir -p; that is, we create parent + * cgroups as necessary. The group parameters are also set according to + * the corresponding preferences. + * + * @param aGroup The name of the group. + * @return true if we successfully created the cgroup, or if it already + * exists. Otherwise, return false. + */ +static bool +EnsureCpuCGroupExists(const nsACString &aGroup) +{ + NS_NAMED_LITERAL_CSTRING(kDevCpuCtl, "/dev/cpuctl/"); + NS_NAMED_LITERAL_CSTRING(kSlash, "/"); + + nsAutoCString groupName(aGroup); + HAL_LOG("EnsureCpuCGroupExists for group '%s'", groupName.get()); + + nsAutoCString prefPrefix("hal.processPriorityManager.gonk.cgroups."); + + /* If cgroup is not empty, append the cgroup name and a dot to obtain the + * group specific preferences. */ + if (!aGroup.IsEmpty()) { + prefPrefix += aGroup + NS_LITERAL_CSTRING("."); + } + + nsAutoCString cpuSharesPref(prefPrefix + NS_LITERAL_CSTRING("cpu_shares")); + int cpuShares = Preferences::GetInt(cpuSharesPref.get()); + + nsAutoCString cpuNotifyOnMigratePref(prefPrefix + + NS_LITERAL_CSTRING("cpu_notify_on_migrate")); + int cpuNotifyOnMigrate = Preferences::GetInt(cpuNotifyOnMigratePref.get()); + + // Create mCGroup and its parent directories, as necessary. + nsCString cgroupIter = aGroup + kSlash; + + int32_t offset = 0; + while ((offset = cgroupIter.FindChar('/', offset)) != -1) { + nsAutoCString path = kDevCpuCtl + Substring(cgroupIter, 0, offset); + int rv = mkdir(path.get(), 0744); + + if (rv == -1 && errno != EEXIST) { + HAL_LOG("Could not create the %s control group.", path.get()); + return false; + } + + offset++; + } + HAL_LOG("EnsureCpuCGroupExists created group '%s'", groupName.get()); + + nsAutoCString pathPrefix(kDevCpuCtl + aGroup + kSlash); + nsAutoCString cpuSharesPath(pathPrefix + NS_LITERAL_CSTRING("cpu.shares")); + if (cpuShares && !WriteSysFile(cpuSharesPath.get(), + nsPrintfCString("%d", cpuShares).get())) { + HAL_LOG("Could not set the cpu share for group %s", cpuSharesPath.get()); + return false; + } + + nsAutoCString notifyOnMigratePath(pathPrefix + + NS_LITERAL_CSTRING("cpu.notify_on_migrate")); + if (!WriteSysFile(notifyOnMigratePath.get(), + nsPrintfCString("%d", cpuNotifyOnMigrate).get())) { + HAL_LOG("Could not set the cpu migration notification flag for group %s", + notifyOnMigratePath.get()); + return false; + } + + return true; +} + +static bool +EnsureMemCGroupExists(const nsACString &aGroup) +{ + NS_NAMED_LITERAL_CSTRING(kMemCtl, "/sys/fs/cgroup/memory/"); + NS_NAMED_LITERAL_CSTRING(kSlash, "/"); + + nsAutoCString groupName(aGroup); + HAL_LOG("EnsureMemCGroupExists for group '%s'", groupName.get()); + + nsAutoCString prefPrefix("hal.processPriorityManager.gonk.cgroups."); + + /* If cgroup is not empty, append the cgroup name and a dot to obtain the + * group specific preferences. */ + if (!aGroup.IsEmpty()) { + prefPrefix += aGroup + NS_LITERAL_CSTRING("."); + } + + nsAutoCString memSwappinessPref(prefPrefix + NS_LITERAL_CSTRING("memory_swappiness")); + int memSwappiness = Preferences::GetInt(memSwappinessPref.get()); + + // Create mCGroup and its parent directories, as necessary. + nsCString cgroupIter = aGroup + kSlash; + + int32_t offset = 0; + while ((offset = cgroupIter.FindChar('/', offset)) != -1) { + nsAutoCString path = kMemCtl + Substring(cgroupIter, 0, offset); + int rv = mkdir(path.get(), 0744); + + if (rv == -1 && errno != EEXIST) { + HAL_LOG("Could not create the %s control group.", path.get()); + return false; + } + + offset++; + } + HAL_LOG("EnsureMemCGroupExists created group '%s'", groupName.get()); + + nsAutoCString pathPrefix(kMemCtl + aGroup + kSlash); + nsAutoCString memSwappinessPath(pathPrefix + NS_LITERAL_CSTRING("memory.swappiness")); + if (!WriteSysFile(memSwappinessPath.get(), + nsPrintfCString("%d", memSwappiness).get())) { + HAL_LOG("Could not set the memory.swappiness for group %s", memSwappinessPath.get()); + return false; + } + HAL_LOG("Set memory.swappiness for group %s to %d", memSwappinessPath.get(), memSwappiness); + + return true; +} + +PriorityClass::PriorityClass(ProcessPriority aPriority) + : mPriority(aPriority) + , mOomScoreAdj(0) + , mKillUnderKB(0) + , mCpuCGroupProcsFd(-1) + , mMemCGroupProcsFd(-1) +{ + DebugOnly<nsresult> rv; + + rv = Preferences::GetInt(PriorityPrefName("OomScoreAdjust").get(), + &mOomScoreAdj); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Missing oom_score_adj preference"); + + rv = Preferences::GetInt(PriorityPrefName("KillUnderKB").get(), + &mKillUnderKB); + + rv = Preferences::GetCString(PriorityPrefName("cgroup").get(), &mGroup); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Missing control group preference"); + + if (EnsureCpuCGroupExists(mGroup)) { + mCpuCGroupProcsFd = OpenCpuCGroupProcs(); + } + if (EnsureMemCGroupExists(mGroup)) { + mMemCGroupProcsFd = OpenMemCGroupProcs(); + } +} + +PriorityClass::~PriorityClass() +{ + if (mCpuCGroupProcsFd != -1) { + close(mCpuCGroupProcsFd); + } + if (mMemCGroupProcsFd != -1) { + close(mMemCGroupProcsFd); + } +} + +PriorityClass::PriorityClass(const PriorityClass& aOther) + : mPriority(aOther.mPriority) + , mOomScoreAdj(aOther.mOomScoreAdj) + , mKillUnderKB(aOther.mKillUnderKB) + , mGroup(aOther.mGroup) +{ + mCpuCGroupProcsFd = OpenCpuCGroupProcs(); + mMemCGroupProcsFd = OpenMemCGroupProcs(); +} + +PriorityClass& PriorityClass::operator=(const PriorityClass& aOther) +{ + mPriority = aOther.mPriority; + mOomScoreAdj = aOther.mOomScoreAdj; + mKillUnderKB = aOther.mKillUnderKB; + mGroup = aOther.mGroup; + mCpuCGroupProcsFd = OpenCpuCGroupProcs(); + mMemCGroupProcsFd = OpenMemCGroupProcs(); + return *this; +} + +void PriorityClass::AddProcess(int aPid) +{ + if (mCpuCGroupProcsFd >= 0) { + nsPrintfCString str("%d", aPid); + + if (write(mCpuCGroupProcsFd, str.get(), strlen(str.get())) < 0) { + HAL_ERR("Couldn't add PID %d to the %s cpu control group", aPid, mGroup.get()); + } + } + if (mMemCGroupProcsFd >= 0) { + nsPrintfCString str("%d", aPid); + + if (write(mMemCGroupProcsFd, str.get(), strlen(str.get())) < 0) { + HAL_ERR("Couldn't add PID %d to the %s memory control group", aPid, mGroup.get()); + } + } +} + +/** + * Get the PriorityClass associated with the given ProcessPriority. + * + * If you pass an invalid ProcessPriority value, we return null. + * + * The pointers returned here are owned by GetPriorityClass (don't free them + * yourself). They are guaranteed to stick around until shutdown. + */ +PriorityClass* +GetPriorityClass(ProcessPriority aPriority) +{ + static StaticAutoPtr<nsTArray<PriorityClass>> priorityClasses; + + // Initialize priorityClasses if this is the first time we're running this + // method. + if (!priorityClasses) { + priorityClasses = new nsTArray<PriorityClass>(); + ClearOnShutdown(&priorityClasses); + + for (int32_t i = 0; i < NUM_PROCESS_PRIORITY; i++) { + priorityClasses->AppendElement(PriorityClass(ProcessPriority(i))); + } + } + + if (aPriority < 0 || + static_cast<uint32_t>(aPriority) >= priorityClasses->Length()) { + return nullptr; + } + + return &(*priorityClasses)[aPriority]; +} + +static void +EnsureKernelLowMemKillerParamsSet() +{ + static bool kernelLowMemKillerParamsSet; + if (kernelLowMemKillerParamsSet) { + return; + } + kernelLowMemKillerParamsSet = true; + + HAL_LOG("Setting kernel's low-mem killer parameters."); + + // Set /sys/module/lowmemorykiller/parameters/{adj,minfree,notify_trigger} + // according to our prefs. These files let us tune when the kernel kills + // processes when we're low on memory, and when it notifies us that we're + // running low on available memory. + // + // adj and minfree are both comma-separated lists of integers. If adj="A,B" + // and minfree="X,Y", then the kernel will kill processes with oom_adj + // A or higher once we have fewer than X pages of memory free, and will kill + // processes with oom_adj B or higher once we have fewer than Y pages of + // memory free. + // + // notify_trigger is a single integer. If we set notify_trigger=Z, then + // we'll get notified when there are fewer than Z pages of memory free. (See + // GonkMemoryPressureMonitoring.cpp.) + + // Build the adj and minfree strings. + nsAutoCString adjParams; + nsAutoCString minfreeParams; + + DebugOnly<int32_t> lowerBoundOfNextOomScoreAdj = OOM_SCORE_ADJ_MIN - 1; + DebugOnly<int32_t> lowerBoundOfNextKillUnderKB = 0; + int32_t countOfLowmemorykillerParametersSets = 0; + + long page_size = sysconf(_SC_PAGESIZE); + + for (int i = NUM_PROCESS_PRIORITY - 1; i >= 0; i--) { + // The system doesn't function correctly if we're missing these prefs, so + // crash loudly. + + PriorityClass* pc = GetPriorityClass(static_cast<ProcessPriority>(i)); + + int32_t oomScoreAdj = pc->OomScoreAdj(); + int32_t killUnderKB = pc->KillUnderKB(); + + if (killUnderKB == 0) { + // ProcessPriority values like PROCESS_PRIORITY_FOREGROUND_KEYBOARD, + // which has only OomScoreAdjust but lacks KillUnderMB value, will not + // create new LMK parameters. + continue; + } + + // The LMK in kernel silently malfunctions if we assign the parameters + // in non-increasing order, so we add this assertion here. See bug 887192. + MOZ_ASSERT(oomScoreAdj > lowerBoundOfNextOomScoreAdj); + MOZ_ASSERT(killUnderKB > lowerBoundOfNextKillUnderKB); + + // The LMK in kernel only accept 6 sets of LMK parameters. See bug 914728. + MOZ_ASSERT(countOfLowmemorykillerParametersSets < 6); + + // adj is in oom_adj units. + adjParams.AppendPrintf("%d,", OomAdjOfOomScoreAdj(oomScoreAdj)); + + // minfree is in pages. + minfreeParams.AppendPrintf("%ld,", killUnderKB * 1024 / page_size); + + lowerBoundOfNextOomScoreAdj = oomScoreAdj; + lowerBoundOfNextKillUnderKB = killUnderKB; + countOfLowmemorykillerParametersSets++; + } + + // Strip off trailing commas. + adjParams.Cut(adjParams.Length() - 1, 1); + minfreeParams.Cut(minfreeParams.Length() - 1, 1); + if (!adjParams.IsEmpty() && !minfreeParams.IsEmpty()) { + WriteSysFile("/sys/module/lowmemorykiller/parameters/adj", adjParams.get()); + WriteSysFile("/sys/module/lowmemorykiller/parameters/minfree", + minfreeParams.get()); + } + + // Set the low-memory-notification threshold. + int32_t lowMemNotifyThresholdKB; + if (NS_SUCCEEDED(Preferences::GetInt( + "hal.processPriorityManager.gonk.notifyLowMemUnderKB", + &lowMemNotifyThresholdKB))) { + + // notify_trigger is in pages. + WriteSysFile("/sys/module/lowmemorykiller/parameters/notify_trigger", + nsPrintfCString("%ld", lowMemNotifyThresholdKB * 1024 / page_size).get()); + } + + // Ensure OOM events appear in logcat + RefPtr<OomVictimLogger> oomLogger = new OomVictimLogger(); + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->AddObserver(oomLogger, "ipc:content-shutdown", false); + } +} + +void +SetProcessPriority(int aPid, ProcessPriority aPriority, uint32_t aLRU) +{ + HAL_LOG("SetProcessPriority(pid=%d, priority=%d, LRU=%u)", + aPid, aPriority, aLRU); + + // If this is the first time SetProcessPriority was called, set the kernel's + // OOM parameters according to our prefs. + // + // We could/should do this on startup instead of waiting for the first + // SetProcessPriorityCall. But in practice, the master process needs to set + // its priority early in the game, so we can reasonably rely on + // SetProcessPriority being called early in startup. + EnsureKernelLowMemKillerParamsSet(); + + PriorityClass* pc = GetPriorityClass(aPriority); + + int oomScoreAdj = pc->OomScoreAdj(); + + RoundOomScoreAdjUpWithLRU(oomScoreAdj, aLRU); + + // We try the newer interface first, and fall back to the older interface + // on failure. + if (!WriteSysFile(nsPrintfCString("/proc/%d/oom_score_adj", aPid).get(), + nsPrintfCString("%d", oomScoreAdj).get())) + { + WriteSysFile(nsPrintfCString("/proc/%d/oom_adj", aPid).get(), + nsPrintfCString("%d", OomAdjOfOomScoreAdj(oomScoreAdj)).get()); + } + + HAL_LOG("Assigning pid %d to cgroup %s", aPid, pc->CGroup().get()); + pc->AddProcess(aPid); +} + +static bool +IsValidRealTimePriority(int aValue, int aSchedulePolicy) +{ + return (aValue >= sched_get_priority_min(aSchedulePolicy)) && + (aValue <= sched_get_priority_max(aSchedulePolicy)); +} + +static void +SetThreadNiceValue(pid_t aTid, ThreadPriority aThreadPriority, int aValue) +{ + MOZ_ASSERT(aThreadPriority < NUM_THREAD_PRIORITY); + MOZ_ASSERT(aThreadPriority >= 0); + + HAL_LOG("Setting thread %d to priority level %s; nice level %d", + aTid, ThreadPriorityToString(aThreadPriority), aValue); + int rv = setpriority(PRIO_PROCESS, aTid, aValue); + + if (rv) { + HAL_LOG("Failed to set thread %d to priority level %s; error %s", aTid, + ThreadPriorityToString(aThreadPriority), strerror(errno)); + } +} + +static void +SetRealTimeThreadPriority(pid_t aTid, + ThreadPriority aThreadPriority, + int aValue) +{ + int policy = SCHED_FIFO; + + MOZ_ASSERT(aThreadPriority < NUM_THREAD_PRIORITY); + MOZ_ASSERT(aThreadPriority >= 0); + MOZ_ASSERT(IsValidRealTimePriority(aValue, policy), "Invalid real time priority"); + + // Setting real time priorities requires using sched_setscheduler + HAL_LOG("Setting thread %d to priority level %s; Real Time priority %d, " + "Schedule FIFO", aTid, ThreadPriorityToString(aThreadPriority), + aValue); + sched_param schedParam; + schedParam.sched_priority = aValue; + int rv = sched_setscheduler(aTid, policy, &schedParam); + + if (rv) { + HAL_LOG("Failed to set thread %d to real time priority level %s; error %s", + aTid, ThreadPriorityToString(aThreadPriority), strerror(errno)); + } +} + +/* + * Used to store the nice value adjustments and real time priorities for the + * various thread priority levels. + */ +struct ThreadPriorityPrefs { + bool initialized; + struct { + int nice; + int realTime; + } priorities[NUM_THREAD_PRIORITY]; +}; + +/* + * Reads the preferences for the various process priority levels and sets up + * watchers so that if they're dynamically changed the change is reflected on + * the appropriate variables. + */ +void +EnsureThreadPriorityPrefs(ThreadPriorityPrefs* prefs) +{ + if (prefs->initialized) { + return; + } + + for (int i = THREAD_PRIORITY_COMPOSITOR; i < NUM_THREAD_PRIORITY; i++) { + ThreadPriority priority = static_cast<ThreadPriority>(i); + + // Read the nice values + const char* threadPriorityStr = ThreadPriorityToString(priority); + nsPrintfCString niceStr("hal.gonk.%s.nice", threadPriorityStr); + Preferences::AddIntVarCache(&prefs->priorities[i].nice, niceStr.get()); + + // Read the real-time priorities + nsPrintfCString realTimeStr("hal.gonk.%s.rt_priority", threadPriorityStr); + Preferences::AddIntVarCache(&prefs->priorities[i].realTime, + realTimeStr.get()); + } + + prefs->initialized = true; +} + +static void +DoSetThreadPriority(pid_t aTid, hal::ThreadPriority aThreadPriority) +{ + // See bug 999115, we can only read preferences on the main thread otherwise + // we create a race condition in HAL + MOZ_ASSERT(NS_IsMainThread(), "Can only set thread priorities on main thread"); + MOZ_ASSERT(aThreadPriority >= 0); + + static ThreadPriorityPrefs prefs = { 0 }; + EnsureThreadPriorityPrefs(&prefs); + + switch (aThreadPriority) { + case THREAD_PRIORITY_COMPOSITOR: + break; + default: + HAL_ERR("Unrecognized thread priority %d; Doing nothing", + aThreadPriority); + return; + } + + int realTimePriority = prefs.priorities[aThreadPriority].realTime; + + if (IsValidRealTimePriority(realTimePriority, SCHED_FIFO)) { + SetRealTimeThreadPriority(aTid, aThreadPriority, realTimePriority); + return; + } + + SetThreadNiceValue(aTid, aThreadPriority, + prefs.priorities[aThreadPriority].nice); +} + +namespace { + +/** + * This class sets the priority of threads given the kernel thread's id and a + * value taken from hal::ThreadPriority. + * + * This runnable must always be dispatched to the main thread otherwise it will fail. + * We have to run this from the main thread since preferences can only be read on + * main thread. + */ +class SetThreadPriorityRunnable : public Runnable +{ +public: + SetThreadPriorityRunnable(pid_t aThreadId, hal::ThreadPriority aThreadPriority) + : mThreadId(aThreadId) + , mThreadPriority(aThreadPriority) + { } + + NS_IMETHOD Run() override + { + NS_ASSERTION(NS_IsMainThread(), "Can only set thread priorities on main thread"); + hal_impl::DoSetThreadPriority(mThreadId, mThreadPriority); + return NS_OK; + } + +private: + pid_t mThreadId; + hal::ThreadPriority mThreadPriority; +}; + +} // namespace + +void +SetCurrentThreadPriority(ThreadPriority aThreadPriority) +{ + pid_t threadId = gettid(); + hal_impl::SetThreadPriority(threadId, aThreadPriority); +} + +void +SetThreadPriority(PlatformThreadId aThreadId, + ThreadPriority aThreadPriority) +{ + switch (aThreadPriority) { + case THREAD_PRIORITY_COMPOSITOR: { + nsCOMPtr<nsIRunnable> runnable = + new SetThreadPriorityRunnable(aThreadId, aThreadPriority); + NS_DispatchToMainThread(runnable); + break; + } + default: + HAL_LOG("Unrecognized thread priority %d; Doing nothing", + aThreadPriority); + return; + } +} + +void +FactoryReset(FactoryResetReason& aReason) +{ + nsCOMPtr<nsIRecoveryService> recoveryService = + do_GetService("@mozilla.org/recovery-service;1"); + if (!recoveryService) { + NS_WARNING("Could not get recovery service!"); + return; + } + + if (aReason == FactoryResetReason::Wipe) { + recoveryService->FactoryReset("wipe"); + } else if (aReason == FactoryResetReason::Root) { + recoveryService->FactoryReset("root"); + } else { + recoveryService->FactoryReset("normal"); + } +} + +} // hal_impl +} // mozilla diff --git a/hal/gonk/GonkSensor.cpp b/hal/gonk/GonkSensor.cpp new file mode 100644 index 000000000..7bd2d3c9b --- /dev/null +++ b/hal/gonk/GonkSensor.cpp @@ -0,0 +1,861 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright 2012 Mozilla Foundation and Mozilla contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <pthread.h> +#include <stdio.h> + +#include "mozilla/DebugOnly.h" +#include "mozilla/Saturate.h" + +#include "base/basictypes.h" +#include "base/thread.h" +#include "base/task.h" + +#include "GonkSensorsInterface.h" +#include "GonkSensorsPollInterface.h" +#include "GonkSensorsRegistryInterface.h" +#include "Hal.h" +#include "HalLog.h" +#include "HalSensor.h" +#include "hardware/sensors.h" +#include "nsThreadUtils.h" + +using namespace mozilla::hal; + +namespace mozilla { + +// +// Internal implementation +// + +// The value from SensorDevice.h (Android) +#define DEFAULT_DEVICE_POLL_RATE 200000000 /*200ms*/ +// ProcessOrientation.cpp needs smaller poll rate to detect delay between +// different orientation angles +#define ACCELEROMETER_POLL_RATE 66667000 /*66.667ms*/ + +// This is present in Android from API level 18 onwards, which is 4.3. We might +// be building on something before 4.3, so use a local define for its value +#define MOZ_SENSOR_TYPE_GAME_ROTATION_VECTOR 15 + +double radToDeg(double a) { + return a * (180.0 / M_PI); +} + +static SensorType +HardwareSensorToHalSensor(int type) +{ + switch(type) { + case SENSOR_TYPE_ORIENTATION: + return SENSOR_ORIENTATION; + case SENSOR_TYPE_ACCELEROMETER: + return SENSOR_ACCELERATION; + case SENSOR_TYPE_PROXIMITY: + return SENSOR_PROXIMITY; + case SENSOR_TYPE_LIGHT: + return SENSOR_LIGHT; + case SENSOR_TYPE_GYROSCOPE: + return SENSOR_GYROSCOPE; + case SENSOR_TYPE_LINEAR_ACCELERATION: + return SENSOR_LINEAR_ACCELERATION; + case SENSOR_TYPE_ROTATION_VECTOR: + return SENSOR_ROTATION_VECTOR; + case MOZ_SENSOR_TYPE_GAME_ROTATION_VECTOR: + return SENSOR_GAME_ROTATION_VECTOR; + default: + return SENSOR_UNKNOWN; + } +} + +static SensorAccuracyType +HardwareStatusToHalAccuracy(int status) { + return static_cast<SensorAccuracyType>(status); +} + +static int +HalSensorToHardwareSensor(SensorType type) +{ + switch(type) { + case SENSOR_ORIENTATION: + return SENSOR_TYPE_ORIENTATION; + case SENSOR_ACCELERATION: + return SENSOR_TYPE_ACCELEROMETER; + case SENSOR_PROXIMITY: + return SENSOR_TYPE_PROXIMITY; + case SENSOR_LIGHT: + return SENSOR_TYPE_LIGHT; + case SENSOR_GYROSCOPE: + return SENSOR_TYPE_GYROSCOPE; + case SENSOR_LINEAR_ACCELERATION: + return SENSOR_TYPE_LINEAR_ACCELERATION; + case SENSOR_ROTATION_VECTOR: + return SENSOR_TYPE_ROTATION_VECTOR; + case SENSOR_GAME_ROTATION_VECTOR: + return MOZ_SENSOR_TYPE_GAME_ROTATION_VECTOR; + default: + return -1; + } +} + +static int +SensorseventStatus(const sensors_event_t& data) +{ + int type = data.type; + switch(type) { + case SENSOR_ORIENTATION: + return data.orientation.status; + case SENSOR_LINEAR_ACCELERATION: + case SENSOR_ACCELERATION: + return data.acceleration.status; + case SENSOR_GYROSCOPE: + return data.gyro.status; + } + + return SENSOR_STATUS_UNRELIABLE; +} + +class SensorRunnable : public Runnable +{ +public: + SensorRunnable(const sensors_event_t& data, const sensor_t* sensors, ssize_t size) + { + mSensorData.sensor() = HardwareSensorToHalSensor(data.type); + mSensorData.accuracy() = HardwareStatusToHalAccuracy(SensorseventStatus(data)); + mSensorData.timestamp() = data.timestamp; + if (mSensorData.sensor() == SENSOR_GYROSCOPE) { + // libhardware returns gyro as rad. convert. + mSensorValues.AppendElement(radToDeg(data.data[0])); + mSensorValues.AppendElement(radToDeg(data.data[1])); + mSensorValues.AppendElement(radToDeg(data.data[2])); + } else if (mSensorData.sensor() == SENSOR_PROXIMITY) { + mSensorValues.AppendElement(data.data[0]); + mSensorValues.AppendElement(0); + + // Determine the maxRange for this sensor. + for (ssize_t i = 0; i < size; i++) { + if (sensors[i].type == SENSOR_TYPE_PROXIMITY) { + mSensorValues.AppendElement(sensors[i].maxRange); + } + } + } else if (mSensorData.sensor() == SENSOR_LIGHT) { + mSensorValues.AppendElement(data.data[0]); + } else if (mSensorData.sensor() == SENSOR_ROTATION_VECTOR) { + mSensorValues.AppendElement(data.data[0]); + mSensorValues.AppendElement(data.data[1]); + mSensorValues.AppendElement(data.data[2]); + if (data.data[3] == 0.0) { + // data.data[3] was optional in Android <= API level 18. It can be computed from 012, + // but it's better to take the actual value if one is provided. The computation is + // v = 1 - d[0]*d[0] - d[1]*d[1] - d[2]*d[2] + // d[3] = v > 0 ? sqrt(v) : 0; + // I'm assuming that it will be 0 if it's not passed in. (The values form a unit + // quaternion, so the angle can be computed from the direction vector.) + float sx = data.data[0], sy = data.data[1], sz = data.data[2]; + float v = 1.0f - sx*sx - sy*sy - sz*sz; + mSensorValues.AppendElement(v > 0.0f ? sqrt(v) : 0.0f); + } else { + mSensorValues.AppendElement(data.data[3]); + } + } else if (mSensorData.sensor() == SENSOR_GAME_ROTATION_VECTOR) { + mSensorValues.AppendElement(data.data[0]); + mSensorValues.AppendElement(data.data[1]); + mSensorValues.AppendElement(data.data[2]); + mSensorValues.AppendElement(data.data[3]); + } else { + mSensorValues.AppendElement(data.data[0]); + mSensorValues.AppendElement(data.data[1]); + mSensorValues.AppendElement(data.data[2]); + } + mSensorData.values() = mSensorValues; + } + + ~SensorRunnable() {} + + NS_IMETHOD Run() override + { + NotifySensorChange(mSensorData); + return NS_OK; + } + +private: + SensorData mSensorData; + AutoTArray<float, 4> mSensorValues; +}; + +namespace hal_impl { + +static DebugOnly<int> sSensorRefCount[NUM_SENSOR_TYPE]; +static base::Thread* sPollingThread; +static sensors_poll_device_t* sSensorDevice; +static sensors_module_t* sSensorModule; + +static void +PollSensors() +{ + const size_t numEventMax = 16; + sensors_event_t buffer[numEventMax]; + const sensor_t* sensors; + int size = sSensorModule->get_sensors_list(sSensorModule, &sensors); + + do { + // didn't check sSensorDevice because already be done on creating pollingThread. + int n = sSensorDevice->poll(sSensorDevice, buffer, numEventMax); + if (n < 0) { + HAL_ERR("Error polling for sensor data (err=%d)", n); + break; + } + + for (int i = 0; i < n; ++i) { + // FIXME: bug 802004, add proper support for the magnetic field sensor. + if (buffer[i].type == SENSOR_TYPE_MAGNETIC_FIELD) + continue; + + // Bug 938035, transfer HAL data for orientation sensor to meet w3c spec + // ex: HAL report alpha=90 means East but alpha=90 means West in w3c spec + if (buffer[i].type == SENSOR_TYPE_ORIENTATION) { + buffer[i].orientation.azimuth = 360 - buffer[i].orientation.azimuth; + buffer[i].orientation.pitch = -buffer[i].orientation.pitch; + buffer[i].orientation.roll = -buffer[i].orientation.roll; + } + + if (HardwareSensorToHalSensor(buffer[i].type) == SENSOR_UNKNOWN) { + // Emulator is broken and gives us events without types set + int index; + for (index = 0; index < size; index++) { + if (sensors[index].handle == buffer[i].sensor) { + break; + } + } + if (index < size && + HardwareSensorToHalSensor(sensors[index].type) != SENSOR_UNKNOWN) { + buffer[i].type = sensors[index].type; + } else { + HAL_LOG("Could not determine sensor type of event"); + continue; + } + } + + NS_DispatchToMainThread(new SensorRunnable(buffer[i], sensors, size)); + } + } while (true); +} + +static void +SwitchSensor(bool aActivate, sensor_t aSensor, pthread_t aThreadId) +{ + int index = HardwareSensorToHalSensor(aSensor.type); + + MOZ_ASSERT(sSensorRefCount[index] || aActivate); + + sSensorDevice->activate(sSensorDevice, aSensor.handle, aActivate); + + if (aActivate) { + if (aSensor.type == SENSOR_TYPE_ACCELEROMETER) { + sSensorDevice->setDelay(sSensorDevice, aSensor.handle, + ACCELEROMETER_POLL_RATE); + } else { + sSensorDevice->setDelay(sSensorDevice, aSensor.handle, + DEFAULT_DEVICE_POLL_RATE); + } + } + + if (aActivate) { + sSensorRefCount[index]++; + } else { + sSensorRefCount[index]--; + } +} + +static void +SetSensorState(SensorType aSensor, bool activate) +{ + int type = HalSensorToHardwareSensor(aSensor); + const sensor_t* sensors = nullptr; + + int size = sSensorModule->get_sensors_list(sSensorModule, &sensors); + for (ssize_t i = 0; i < size; i++) { + if (sensors[i].type == type) { + SwitchSensor(activate, sensors[i], pthread_self()); + break; + } + } +} + +static void +EnableSensorNotificationsInternal(SensorType aSensor) +{ + if (!sSensorModule) { + hw_get_module(SENSORS_HARDWARE_MODULE_ID, + (hw_module_t const**)&sSensorModule); + if (!sSensorModule) { + HAL_ERR("Can't get sensor HAL module\n"); + return; + } + + sensors_open(&sSensorModule->common, &sSensorDevice); + if (!sSensorDevice) { + sSensorModule = nullptr; + HAL_ERR("Can't get sensor poll device from module \n"); + return; + } + + sensor_t const* sensors; + int count = sSensorModule->get_sensors_list(sSensorModule, &sensors); + for (size_t i=0 ; i<size_t(count) ; i++) { + sSensorDevice->activate(sSensorDevice, sensors[i].handle, 0); + } + } + + if (!sPollingThread) { + sPollingThread = new base::Thread("GonkSensors"); + MOZ_ASSERT(sPollingThread); + // sPollingThread never terminates because poll may never return + sPollingThread->Start(); + sPollingThread->message_loop()->PostTask( + NewRunnableFunction(PollSensors)); + } + + SetSensorState(aSensor, true); +} + +static void +DisableSensorNotificationsInternal(SensorType aSensor) +{ + if (!sSensorModule) { + return; + } + SetSensorState(aSensor, false); +} + +// +// Daemon +// + +typedef detail::SaturateOp<uint32_t> SaturateOpUint32; + +/** + * The poll notification handler receives all events about sensors and + * sensor events. + */ +class SensorsPollNotificationHandler final + : public GonkSensorsPollNotificationHandler +{ +public: + SensorsPollNotificationHandler(GonkSensorsPollInterface* aPollInterface) + : mPollInterface(aPollInterface) + { + MOZ_ASSERT(mPollInterface); + + mPollInterface->SetNotificationHandler(this); + } + + void EnableSensorsByType(SensorsType aType) + { + if (SaturateOpUint32(mClasses[aType].mActivated)++) { + return; + } + + SensorsDeliveryMode deliveryMode = DefaultSensorsDeliveryMode(aType); + + // Old ref-count for the sensor type was 0, so we + // activate all sensors of the type. + for (size_t i = 0; i < mSensors.Length(); ++i) { + if (mSensors[i].mType == aType && + mSensors[i].mDeliveryMode == deliveryMode) { + mPollInterface->EnableSensor(mSensors[i].mId, nullptr); + mPollInterface->SetPeriod(mSensors[i].mId, DefaultSensorPeriod(aType), + nullptr); + } + } + } + + void DisableSensorsByType(SensorsType aType) + { + if (SaturateOpUint32(mClasses[aType].mActivated)-- != 1) { + return; + } + + SensorsDeliveryMode deliveryMode = DefaultSensorsDeliveryMode(aType); + + // Old ref-count for the sensor type was 1, so we + // deactivate all sensors of the type. + for (size_t i = 0; i < mSensors.Length(); ++i) { + if (mSensors[i].mType == aType && + mSensors[i].mDeliveryMode == deliveryMode) { + mPollInterface->DisableSensor(mSensors[i].mId, nullptr); + } + } + } + + void ClearSensorClasses() + { + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mClasses); ++i) { + mClasses[i] = SensorsSensorClass(); + } + } + + void ClearSensors() + { + mSensors.Clear(); + } + + // Methods for SensorsPollNotificationHandler + // + + void ErrorNotification(SensorsError aError) override + { + // XXX: Bug 1206056: Try to repair some of the errors or restart cleanly. + } + + void SensorDetectedNotification(int32_t aId, SensorsType aType, + float aRange, float aResolution, + float aPower, int32_t aMinPeriod, + int32_t aMaxPeriod, + SensorsTriggerMode aTriggerMode, + SensorsDeliveryMode aDeliveryMode) override + { + auto i = FindSensorIndexById(aId); + if (i == -1) { + // Add a new sensor... + i = mSensors.Length(); + mSensors.AppendElement(SensorsSensor(aId, aType, aRange, aResolution, + aPower, aMinPeriod, aMaxPeriod, + aTriggerMode, aDeliveryMode)); + } else { + // ...or update an existing one. + mSensors[i] = SensorsSensor(aId, aType, aRange, aResolution, aPower, + aMinPeriod, aMaxPeriod, aTriggerMode, + aDeliveryMode); + } + + mClasses[aType].UpdateFromSensor(mSensors[i]); + + if (mClasses[aType].mActivated && + mSensors[i].mDeliveryMode == DefaultSensorsDeliveryMode(aType)) { + // The new sensor's type is enabled, so enable sensor. + mPollInterface->EnableSensor(aId, nullptr); + mPollInterface->SetPeriod(mSensors[i].mId, DefaultSensorPeriod(aType), + nullptr); + } + } + + void SensorLostNotification(int32_t aId) override + { + auto i = FindSensorIndexById(aId); + if (i != -1) { + mSensors.RemoveElementAt(i); + } + } + + void EventNotification(int32_t aId, const SensorsEvent& aEvent) override + { + auto i = FindSensorIndexById(aId); + if (i == -1) { + HAL_ERR("Sensor %d not registered", aId); + return; + } + + SensorData sensorData; + auto rv = CreateSensorData(aEvent, mClasses[mSensors[i].mType], + sensorData); + if (NS_FAILED(rv)) { + return; + } + + NotifySensorChange(sensorData); + } + +private: + ssize_t FindSensorIndexById(int32_t aId) const + { + for (size_t i = 0; i < mSensors.Length(); ++i) { + if (mSensors[i].mId == aId) { + return i; + } + } + return -1; + } + + uint64_t DefaultSensorPeriod(SensorsType aType) const + { + return aType == SENSORS_TYPE_ACCELEROMETER ? ACCELEROMETER_POLL_RATE + : DEFAULT_DEVICE_POLL_RATE; + } + + SensorsDeliveryMode DefaultSensorsDeliveryMode(SensorsType aType) const + { + if (aType == SENSORS_TYPE_PROXIMITY || + aType == SENSORS_TYPE_SIGNIFICANT_MOTION) { + return SENSORS_DELIVERY_MODE_IMMEDIATE; + } + return SENSORS_DELIVERY_MODE_BEST_EFFORT; + } + + SensorType HardwareSensorToHalSensor(SensorsType aType) const + { + // FIXME: bug 802004, add proper support for the magnetic-field sensor. + switch (aType) { + case SENSORS_TYPE_ORIENTATION: + return SENSOR_ORIENTATION; + case SENSORS_TYPE_ACCELEROMETER: + return SENSOR_ACCELERATION; + case SENSORS_TYPE_PROXIMITY: + return SENSOR_PROXIMITY; + case SENSORS_TYPE_LIGHT: + return SENSOR_LIGHT; + case SENSORS_TYPE_GYROSCOPE: + return SENSOR_GYROSCOPE; + case SENSORS_TYPE_LINEAR_ACCELERATION: + return SENSOR_LINEAR_ACCELERATION; + case SENSORS_TYPE_ROTATION_VECTOR: + return SENSOR_ROTATION_VECTOR; + case SENSORS_TYPE_GAME_ROTATION_VECTOR: + return SENSOR_GAME_ROTATION_VECTOR; + default: + NS_NOTREACHED("Invalid sensors type"); + } + return SENSOR_UNKNOWN; + } + + SensorAccuracyType HardwareStatusToHalAccuracy(SensorsStatus aStatus) const + { + return static_cast<SensorAccuracyType>(aStatus - 1); + } + + nsresult CreateSensorData(const SensorsEvent& aEvent, + const SensorsSensorClass& aSensorClass, + SensorData& aSensorData) const + { + AutoTArray<float, 4> sensorValues; + + auto sensor = HardwareSensorToHalSensor(aEvent.mType); + + if (sensor == SENSOR_UNKNOWN) { + return NS_ERROR_ILLEGAL_VALUE; + } + + aSensorData.sensor() = sensor; + aSensorData.accuracy() = HardwareStatusToHalAccuracy(aEvent.mStatus); + aSensorData.timestamp() = aEvent.mTimestamp; + + if (aSensorData.sensor() == SENSOR_ORIENTATION) { + // Bug 938035: transfer HAL data for orientation sensor to meet W3C spec + // ex: HAL report alpha=90 means East but alpha=90 means West in W3C spec + sensorValues.AppendElement(360.0 - radToDeg(aEvent.mData.mFloat[0])); + sensorValues.AppendElement(-radToDeg(aEvent.mData.mFloat[1])); + sensorValues.AppendElement(-radToDeg(aEvent.mData.mFloat[2])); + } else if (aSensorData.sensor() == SENSOR_ACCELERATION) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + sensorValues.AppendElement(aEvent.mData.mFloat[1]); + sensorValues.AppendElement(aEvent.mData.mFloat[2]); + } else if (aSensorData.sensor() == SENSOR_PROXIMITY) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + sensorValues.AppendElement(aSensorClass.mMinValue); + sensorValues.AppendElement(aSensorClass.mMaxValue); + } else if (aSensorData.sensor() == SENSOR_LINEAR_ACCELERATION) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + sensorValues.AppendElement(aEvent.mData.mFloat[1]); + sensorValues.AppendElement(aEvent.mData.mFloat[2]); + } else if (aSensorData.sensor() == SENSOR_GYROSCOPE) { + sensorValues.AppendElement(radToDeg(aEvent.mData.mFloat[0])); + sensorValues.AppendElement(radToDeg(aEvent.mData.mFloat[1])); + sensorValues.AppendElement(radToDeg(aEvent.mData.mFloat[2])); + } else if (aSensorData.sensor() == SENSOR_LIGHT) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + } else if (aSensorData.sensor() == SENSOR_ROTATION_VECTOR) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + sensorValues.AppendElement(aEvent.mData.mFloat[1]); + sensorValues.AppendElement(aEvent.mData.mFloat[2]); + sensorValues.AppendElement(aEvent.mData.mFloat[3]); + } else if (aSensorData.sensor() == SENSOR_GAME_ROTATION_VECTOR) { + sensorValues.AppendElement(aEvent.mData.mFloat[0]); + sensorValues.AppendElement(aEvent.mData.mFloat[1]); + sensorValues.AppendElement(aEvent.mData.mFloat[2]); + sensorValues.AppendElement(aEvent.mData.mFloat[3]); + } + + aSensorData.values() = sensorValues; + + return NS_OK; + } + + GonkSensorsPollInterface* mPollInterface; + nsTArray<SensorsSensor> mSensors; + SensorsSensorClass mClasses[SENSORS_NUM_TYPES]; +}; + +static StaticAutoPtr<SensorsPollNotificationHandler> sPollNotificationHandler; + +/** + * This is the notifiaction handler for the Sensors interface. If the backend + * crashes, we can restart it from here. + */ +class SensorsNotificationHandler final : public GonkSensorsNotificationHandler +{ +public: + SensorsNotificationHandler(GonkSensorsInterface* aInterface) + : mInterface(aInterface) + { + MOZ_ASSERT(mInterface); + + mInterface->SetNotificationHandler(this); + } + + void BackendErrorNotification(bool aCrashed) override + { + // XXX: Bug 1206056: restart sensorsd + } + +private: + GonkSensorsInterface* mInterface; +}; + +static StaticAutoPtr<SensorsNotificationHandler> sNotificationHandler; + +/** + * |SensorsRegisterModuleResultHandler| implements the result-handler + * callback for registering the Poll service and activating the first + * sensors. If an error occures during the process, the result handler + * disconnects and closes the backend. + */ +class SensorsRegisterModuleResultHandler final + : public GonkSensorsRegistryResultHandler +{ +public: + SensorsRegisterModuleResultHandler( + uint32_t* aSensorsTypeActivated, + GonkSensorsInterface* aInterface) + : mSensorsTypeActivated(aSensorsTypeActivated) + , mInterface(aInterface) + { + MOZ_ASSERT(mSensorsTypeActivated); + MOZ_ASSERT(mInterface); + } + void OnError(SensorsError aError) override + { + GonkSensorsRegistryResultHandler::OnError(aError); // print error message + Disconnect(); // Registering failed, so close the connection completely + } + void RegisterModule(uint32_t aProtocolVersion) override + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sPollNotificationHandler); + + // Init, step 3: set notification handler for poll service and vice versa + auto pollInterface = mInterface->GetSensorsPollInterface(); + if (!pollInterface) { + Disconnect(); + return; + } + if (NS_FAILED(pollInterface->SetProtocolVersion(aProtocolVersion))) { + Disconnect(); + return; + } + + sPollNotificationHandler = + new SensorsPollNotificationHandler(pollInterface); + + // Init, step 4: activate sensors + for (int i = 0; i < SENSORS_NUM_TYPES; ++i) { + while (mSensorsTypeActivated[i]) { + sPollNotificationHandler->EnableSensorsByType( + static_cast<SensorsType>(i)); + --mSensorsTypeActivated[i]; + } + } + } +public: + void Disconnect() + { + class DisconnectResultHandler final : public GonkSensorsResultHandler + { + public: + void OnError(SensorsError aError) + { + GonkSensorsResultHandler::OnError(aError); // print error message + sNotificationHandler = nullptr; + } + void Disconnect() override + { + sNotificationHandler = nullptr; + } + }; + mInterface->Disconnect(new DisconnectResultHandler()); + } +private: + uint32_t* mSensorsTypeActivated; + GonkSensorsInterface* mInterface; +}; + +/** + * |SensorsConnectResultHandler| implements the result-handler + * callback for starting the Sensors backend. + */ +class SensorsConnectResultHandler final : public GonkSensorsResultHandler +{ +public: + SensorsConnectResultHandler( + uint32_t* aSensorsTypeActivated, + GonkSensorsInterface* aInterface) + : mSensorsTypeActivated(aSensorsTypeActivated) + , mInterface(aInterface) + { + MOZ_ASSERT(mSensorsTypeActivated); + MOZ_ASSERT(mInterface); + } + void OnError(SensorsError aError) override + { + GonkSensorsResultHandler::OnError(aError); // print error message + sNotificationHandler = nullptr; + } + void Connect() override + { + MOZ_ASSERT(NS_IsMainThread()); + + // Init, step 2: register poll service + auto registryInterface = mInterface->GetSensorsRegistryInterface(); + if (!registryInterface) { + return; + } + registryInterface->RegisterModule( + GonkSensorsPollModule::SERVICE_ID, + new SensorsRegisterModuleResultHandler(mSensorsTypeActivated, + mInterface)); + } +private: + uint32_t* mSensorsTypeActivated; + GonkSensorsInterface* mInterface; +}; + +static uint32_t sSensorsTypeActivated[SENSORS_NUM_TYPES]; + +static const SensorsType sSensorsType[] = { + [SENSOR_ORIENTATION] = SENSORS_TYPE_ORIENTATION, + [SENSOR_ACCELERATION] = SENSORS_TYPE_ACCELEROMETER, + [SENSOR_PROXIMITY] = SENSORS_TYPE_PROXIMITY, + [SENSOR_LINEAR_ACCELERATION] = SENSORS_TYPE_LINEAR_ACCELERATION, + [SENSOR_GYROSCOPE] = SENSORS_TYPE_GYROSCOPE, + [SENSOR_LIGHT] = SENSORS_TYPE_LIGHT, + [SENSOR_ROTATION_VECTOR] = SENSORS_TYPE_ROTATION_VECTOR, + [SENSOR_GAME_ROTATION_VECTOR] = SENSORS_TYPE_GAME_ROTATION_VECTOR +}; + +void +EnableSensorNotificationsDaemon(SensorType aSensor) +{ + if ((aSensor < 0) || + (aSensor > static_cast<ssize_t>(MOZ_ARRAY_LENGTH(sSensorsType)))) { + HAL_ERR("Sensor type %d not known", aSensor); + return; // Unsupported sensor type + } + + auto interface = GonkSensorsInterface::GetInstance(); + if (!interface) { + return; + } + + if (sPollNotificationHandler) { + // Everythings already up and running; enable sensor type. + sPollNotificationHandler->EnableSensorsByType(sSensorsType[aSensor]); + return; + } + + ++SaturateOpUint32(sSensorsTypeActivated[sSensorsType[aSensor]]); + + if (sNotificationHandler) { + // We are in the middle of a pending start up; nothing else to do. + return; + } + + // Start up + + MOZ_ASSERT(!sPollNotificationHandler); + MOZ_ASSERT(!sNotificationHandler); + + sNotificationHandler = new SensorsNotificationHandler(interface); + + // Init, step 1: connect to Sensors backend + interface->Connect( + sNotificationHandler, + new SensorsConnectResultHandler(sSensorsTypeActivated, interface)); +} + +void +DisableSensorNotificationsDaemon(SensorType aSensor) +{ + if ((aSensor < 0) || + (aSensor > static_cast<ssize_t>(MOZ_ARRAY_LENGTH(sSensorsType)))) { + HAL_ERR("Sensor type %d not known", aSensor); + return; // Unsupported sensor type + } + + if (sPollNotificationHandler) { + // Everthings up and running; disable sensors type + sPollNotificationHandler->DisableSensorsByType(sSensorsType[aSensor]); + return; + } + + // We might be in the middle of a startup; decrement type's ref-counter. + --SaturateOpUint32(sSensorsTypeActivated[sSensorsType[aSensor]]); + + // TODO: stop sensorsd if all sensors are disabled +} + +// +// Public interface +// + +// TODO: Remove in-Gecko sensors code. Until all devices' base +// images come with sensorsd installed, we have to support the +// in-Gecko implementation as well. So we test for the existance +// of the binary. If it's there, we use it. Otherwise we run the +// old code. +static bool +HasDaemon() +{ + static bool tested; + static bool hasDaemon; + + if (MOZ_UNLIKELY(!tested)) { + hasDaemon = !access("/system/bin/sensorsd", X_OK); + tested = true; + } + + return hasDaemon; +} + +void +EnableSensorNotifications(SensorType aSensor) +{ + if (HasDaemon()) { + EnableSensorNotificationsDaemon(aSensor); + } else { + EnableSensorNotificationsInternal(aSensor); + } +} + +void +DisableSensorNotifications(SensorType aSensor) +{ + if (HasDaemon()) { + DisableSensorNotificationsDaemon(aSensor); + } else { + DisableSensorNotificationsInternal(aSensor); + } +} + +} // hal_impl +} // mozilla diff --git a/hal/gonk/GonkSensorsHelpers.cpp b/hal/gonk/GonkSensorsHelpers.cpp new file mode 100644 index 000000000..ccc940c7c --- /dev/null +++ b/hal/gonk/GonkSensorsHelpers.cpp @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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 "GonkSensorsHelpers.h" + +namespace mozilla { +namespace hal { + +// +// Unpacking +// + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsEvent& aOut) +{ + nsresult rv = UnpackPDU(aPDU, aOut.mType); + if (NS_FAILED(rv)) { + return rv; + } + rv = UnpackPDU(aPDU, aOut.mTimestamp); + if (NS_FAILED(rv)) { + return rv; + } + rv = UnpackPDU(aPDU, aOut.mStatus); + if (NS_FAILED(rv)) { + return rv; + } + + size_t i = 0; + + switch (aOut.mType) { + case SENSORS_TYPE_MAGNETIC_FIELD_UNCALIBRATED: + case SENSORS_TYPE_GYROSCOPE_UNCALIBRATED: + /* 6 data values */ + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + /* fall through */ + case SENSORS_TYPE_ROTATION_VECTOR: + case SENSORS_TYPE_GAME_ROTATION_VECTOR: + case SENSORS_TYPE_GEOMAGNETIC_ROTATION_VECTOR: + /* 5 data values */ + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + /* fall through */ + case SENSORS_TYPE_ACCELEROMETER: + case SENSORS_TYPE_GEOMAGNETIC_FIELD: + case SENSORS_TYPE_ORIENTATION: + case SENSORS_TYPE_GYROSCOPE: + case SENSORS_TYPE_GRAVITY: + case SENSORS_TYPE_LINEAR_ACCELERATION: + /* 3 data values */ + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + /* fall through */ + case SENSORS_TYPE_LIGHT: + case SENSORS_TYPE_PRESSURE: + case SENSORS_TYPE_TEMPERATURE: + case SENSORS_TYPE_PROXIMITY: + case SENSORS_TYPE_RELATIVE_HUMIDITY: + case SENSORS_TYPE_AMBIENT_TEMPERATURE: + case SENSORS_TYPE_HEART_RATE: + case SENSORS_TYPE_TILT_DETECTOR: + case SENSORS_TYPE_WAKE_GESTURE: + case SENSORS_TYPE_GLANCE_GESTURE: + case SENSORS_TYPE_PICK_UP_GESTURE: + case SENSORS_TYPE_WRIST_TILT_GESTURE: + case SENSORS_TYPE_SIGNIFICANT_MOTION: + case SENSORS_TYPE_STEP_DETECTED: + /* 1 data value */ + rv = UnpackPDU(aPDU, aOut.mData.mFloat[i++]); + if (NS_FAILED(rv)) { + return rv; + } + break; + case SENSORS_TYPE_STEP_COUNTER: + /* 1 data value */ + rv = UnpackPDU(aPDU, aOut.mData.mUint[0]); + if (NS_FAILED(rv)) { + return rv; + } + break; + default: + if (MOZ_HAL_IPC_UNPACK_WARN_IF(true, SensorsEvent)) { + return NS_ERROR_ILLEGAL_VALUE; + } + } + rv = UnpackPDU(aPDU, aOut.mDeliveryMode); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; +} + +} // namespace hal +} // namespace mozilla diff --git a/hal/gonk/GonkSensorsHelpers.h b/hal/gonk/GonkSensorsHelpers.h new file mode 100644 index 000000000..5218af53a --- /dev/null +++ b/hal/gonk/GonkSensorsHelpers.h @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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 hal_gonk_GonkSensorsHelpers_h +#define hal_gonk_GonkSensorsHelpers_h + +#include <mozilla/ipc/DaemonSocketPDU.h> +#include <mozilla/ipc/DaemonSocketPDUHelpers.h> +#include "SensorsTypes.h" + +namespace mozilla { +namespace hal { + +using mozilla::ipc::DaemonSocketPDU; +using mozilla::ipc::DaemonSocketPDUHeader; +using mozilla::ipc::DaemonSocketPDUHelpers::Convert; +using mozilla::ipc::DaemonSocketPDUHelpers::PackPDU; +using mozilla::ipc::DaemonSocketPDUHelpers::UnpackPDU; + +using namespace mozilla::ipc::DaemonSocketPDUHelpers; + +// +// Conversion +// +// The functions below convert the input value to the output value's +// type and perform extension tests on the validity of the result. On +// success the output value will be returned in |aOut|. The functions +// return NS_OK on success, or an XPCOM error code otherwise. +// +// See the documentation of the HAL IPC framework for more information +// on conversion functions. +// + +nsresult +Convert(int32_t aIn, SensorsStatus& aOut) +{ + static const uint8_t sStatus[] = { + [0] = SENSORS_STATUS_NO_CONTACT, // '-1' + [1] = SENSORS_STATUS_UNRELIABLE, // '0' + [2] = SENSORS_STATUS_ACCURACY_LOW, // '1' + [3] = SENSORS_STATUS_ACCURACY_MEDIUM, // '2' + [4] = SENSORS_STATUS_ACCURACY_HIGH // '3' + }; + static const int8_t sOffset = -1; // '-1' is the lower bound of the status + + if (MOZ_HAL_IPC_CONVERT_WARN_IF(aIn < sOffset, int32_t, SensorsStatus) || + MOZ_HAL_IPC_CONVERT_WARN_IF( + aIn >= (static_cast<ssize_t>(MOZ_ARRAY_LENGTH(sStatus)) + sOffset), + int32_t, SensorsStatus)) { + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = static_cast<SensorsStatus>(sStatus[aIn - sOffset]); + return NS_OK; +} + +nsresult +Convert(uint8_t aIn, SensorsDeliveryMode& aOut) +{ + static const uint8_t sMode[] = { + [0x00] = SENSORS_DELIVERY_MODE_BEST_EFFORT, + [0x01] = SENSORS_DELIVERY_MODE_IMMEDIATE + }; + if (MOZ_HAL_IPC_CONVERT_WARN_IF( + aIn >= MOZ_ARRAY_LENGTH(sMode), uint8_t, SensorsDeliveryMode)) { + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = static_cast<SensorsDeliveryMode>(sMode[aIn]); + return NS_OK; +} + +nsresult +Convert(uint8_t aIn, SensorsError& aOut) +{ + static const uint8_t sError[] = { + [0x00] = SENSORS_ERROR_NONE, + [0x01] = SENSORS_ERROR_FAIL, + [0x02] = SENSORS_ERROR_NOT_READY, + [0x03] = SENSORS_ERROR_NOMEM, + [0x04] = SENSORS_ERROR_BUSY, + [0x05] = SENSORS_ERROR_DONE, + [0x06] = SENSORS_ERROR_UNSUPPORTED, + [0x07] = SENSORS_ERROR_PARM_INVALID + }; + if (MOZ_HAL_IPC_CONVERT_WARN_IF( + aIn >= MOZ_ARRAY_LENGTH(sError), uint8_t, SensorsError)) { + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = static_cast<SensorsError>(sError[aIn]); + return NS_OK; +} + +nsresult +Convert(uint8_t aIn, SensorsTriggerMode& aOut) +{ + static const uint8_t sMode[] = { + [0x00] = SENSORS_TRIGGER_MODE_CONTINUOUS, + [0x01] = SENSORS_TRIGGER_MODE_ON_CHANGE, + [0x02] = SENSORS_TRIGGER_MODE_ONE_SHOT, + [0x03] = SENSORS_TRIGGER_MODE_SPECIAL + }; + if (MOZ_HAL_IPC_CONVERT_WARN_IF( + aIn >= MOZ_ARRAY_LENGTH(sMode), uint8_t, SensorsTriggerMode)) { + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = static_cast<SensorsTriggerMode>(sMode[aIn]); + return NS_OK; +} + +nsresult +Convert(uint32_t aIn, SensorsType& aOut) +{ + static const uint8_t sType[] = { + [0x00] = 0, // invalid, required by gcc + [0x01] = SENSORS_TYPE_ACCELEROMETER, + [0x02] = SENSORS_TYPE_GEOMAGNETIC_FIELD, + [0x03] = SENSORS_TYPE_ORIENTATION, + [0x04] = SENSORS_TYPE_GYROSCOPE, + [0x05] = SENSORS_TYPE_LIGHT, + [0x06] = SENSORS_TYPE_PRESSURE, + [0x07] = SENSORS_TYPE_TEMPERATURE, + [0x08] = SENSORS_TYPE_PROXIMITY, + [0x09] = SENSORS_TYPE_GRAVITY, + [0x0a] = SENSORS_TYPE_LINEAR_ACCELERATION, + [0x0b] = SENSORS_TYPE_ROTATION_VECTOR, + [0x0c] = SENSORS_TYPE_RELATIVE_HUMIDITY, + [0x0d] = SENSORS_TYPE_AMBIENT_TEMPERATURE, + [0x0e] = SENSORS_TYPE_MAGNETIC_FIELD_UNCALIBRATED, + [0x0f] = SENSORS_TYPE_GAME_ROTATION_VECTOR, + [0x10] = SENSORS_TYPE_GYROSCOPE_UNCALIBRATED, + [0x11] = SENSORS_TYPE_SIGNIFICANT_MOTION, + [0x12] = SENSORS_TYPE_STEP_DETECTED, + [0x13] = SENSORS_TYPE_STEP_COUNTER, + [0x14] = SENSORS_TYPE_GEOMAGNETIC_ROTATION_VECTOR, + [0x15] = SENSORS_TYPE_HEART_RATE, + [0x16] = SENSORS_TYPE_TILT_DETECTOR, + [0x17] = SENSORS_TYPE_WAKE_GESTURE, + [0x18] = SENSORS_TYPE_GLANCE_GESTURE, + [0x19] = SENSORS_TYPE_PICK_UP_GESTURE, + [0x1a] = SENSORS_TYPE_WRIST_TILT_GESTURE + }; + if (MOZ_HAL_IPC_CONVERT_WARN_IF( + !aIn, uint32_t, SensorsType) || + MOZ_HAL_IPC_CONVERT_WARN_IF( + aIn >= MOZ_ARRAY_LENGTH(sType), uint32_t, SensorsType)) { + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = static_cast<SensorsType>(sType[aIn]); + return NS_OK; +} + +nsresult +Convert(nsresult aIn, SensorsError& aOut) +{ + if (NS_SUCCEEDED(aIn)) { + aOut = SENSORS_ERROR_NONE; + } else if (aIn == NS_ERROR_OUT_OF_MEMORY) { + aOut = SENSORS_ERROR_NOMEM; + } else if (aIn == NS_ERROR_ILLEGAL_VALUE) { + aOut = SENSORS_ERROR_PARM_INVALID; + } else { + aOut = SENSORS_ERROR_FAIL; + } + return NS_OK; +} + +// +// Packing +// +// Pack functions store a value in PDU. See the documentation of the +// HAL IPC framework for more information. +// +// There are currently no sensor-specific pack functions necessary. If +// you add one, put it below. +// + +// +// Unpacking +// +// Unpack function retrieve a value from a PDU. The functions return +// NS_OK on success, or an XPCOM error code otherwise. On sucess, the +// returned value is stored in the second argument |aOut|. +// +// See the documentation of the HAL IPC framework for more information +// on unpack functions. +// + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsDeliveryMode& aOut) +{ + return UnpackPDU(aPDU, UnpackConversion<uint8_t, SensorsDeliveryMode>(aOut)); +} + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsError& aOut) +{ + return UnpackPDU(aPDU, UnpackConversion<uint8_t, SensorsError>(aOut)); +} + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsEvent& aOut); + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsStatus& aOut) +{ + return UnpackPDU(aPDU, UnpackConversion<int32_t, SensorsStatus>(aOut)); +} + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsTriggerMode& aOut) +{ + return UnpackPDU(aPDU, UnpackConversion<uint8_t, SensorsTriggerMode>(aOut)); +} + +nsresult +UnpackPDU(DaemonSocketPDU& aPDU, SensorsType& aOut) +{ + return UnpackPDU(aPDU, UnpackConversion<uint32_t, SensorsType>(aOut)); +} + +} // namespace hal +} // namespace mozilla + +#endif // hal_gonk_GonkSensorsHelpers_h diff --git a/hal/gonk/GonkSensorsInterface.cpp b/hal/gonk/GonkSensorsInterface.cpp new file mode 100644 index 000000000..51e1ff50c --- /dev/null +++ b/hal/gonk/GonkSensorsInterface.cpp @@ -0,0 +1,494 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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 "GonkSensorsInterface.h" +#include "GonkSensorsPollInterface.h" +#include "GonkSensorsRegistryInterface.h" +#include "HalLog.h" +#include <mozilla/ipc/DaemonSocket.h> +#include <mozilla/ipc/DaemonSocketConnector.h> +#include <mozilla/ipc/ListenSocket.h> + +namespace mozilla { +namespace hal { + +using namespace mozilla::ipc; + +// +// GonkSensorsResultHandler +// + +void +GonkSensorsResultHandler::OnError(SensorsError aError) +{ + HAL_ERR("Received error code %d", static_cast<int>(aError)); +} + +void +GonkSensorsResultHandler::Connect() +{ } + +void +GonkSensorsResultHandler::Disconnect() +{ } + +GonkSensorsResultHandler::~GonkSensorsResultHandler() +{ } + +// +// GonkSensorsNotificationHandler +// + +void +GonkSensorsNotificationHandler::BackendErrorNotification(bool aCrashed) +{ + if (aCrashed) { + HAL_ERR("Sensors backend crashed"); + } else { + HAL_ERR("Error in sensors backend"); + } +} + +GonkSensorsNotificationHandler::~GonkSensorsNotificationHandler() +{ } + +// +// GonkSensorsProtocol +// + +class GonkSensorsProtocol final + : public DaemonSocketIOConsumer + , public GonkSensorsRegistryModule + , public GonkSensorsPollModule +{ +public: + GonkSensorsProtocol(); + + void SetConnection(DaemonSocket* aConnection); + + already_AddRefed<DaemonSocketResultHandler> FetchResultHandler( + const DaemonSocketPDUHeader& aHeader); + + // Methods for |SensorsRegistryModule| and |SensorsPollModule| + // + + nsresult Send(DaemonSocketPDU* aPDU, + DaemonSocketResultHandler* aRes) override; + + // Methods for |DaemonSocketIOConsumer| + // + + void Handle(DaemonSocketPDU& aPDU) override; + void StoreResultHandler(const DaemonSocketPDU& aPDU) override; + +private: + void HandleRegistrySvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes); + void HandlePollSvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes); + + DaemonSocket* mConnection; + nsTArray<RefPtr<DaemonSocketResultHandler>> mResultHandlerQ; +}; + +GonkSensorsProtocol::GonkSensorsProtocol() +{ } + +void +GonkSensorsProtocol::SetConnection(DaemonSocket* aConnection) +{ + mConnection = aConnection; +} + +already_AddRefed<DaemonSocketResultHandler> +GonkSensorsProtocol::FetchResultHandler(const DaemonSocketPDUHeader& aHeader) +{ + MOZ_ASSERT(!NS_IsMainThread()); + + if (aHeader.mOpcode & 0x80) { + return nullptr; // Ignore notifications + } + + RefPtr<DaemonSocketResultHandler> res = mResultHandlerQ.ElementAt(0); + mResultHandlerQ.RemoveElementAt(0); + + return res.forget(); +} + +void +GonkSensorsProtocol::HandleRegistrySvc( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + GonkSensorsRegistryModule::HandleSvc(aHeader, aPDU, aRes); +} + +void +GonkSensorsProtocol::HandlePollSvc( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + GonkSensorsPollModule::HandleSvc(aHeader, aPDU, aRes); +} + +// |SensorsRegistryModule|, |SensorsPollModule| + +nsresult +GonkSensorsProtocol::Send(DaemonSocketPDU* aPDU, + DaemonSocketResultHandler* aRes) +{ + MOZ_ASSERT(mConnection); + MOZ_ASSERT(aPDU); + + aPDU->SetConsumer(this); + aPDU->SetResultHandler(aRes); + aPDU->UpdateHeader(); + + if (mConnection->GetConnectionStatus() == SOCKET_DISCONNECTED) { + HAL_ERR("Sensors socket is disconnected"); + return NS_ERROR_FAILURE; + } + + mConnection->SendSocketData(aPDU); // Forward PDU to data channel + + return NS_OK; +} + +// |DaemonSocketIOConsumer| + +void +GonkSensorsProtocol::Handle(DaemonSocketPDU& aPDU) +{ + static void (GonkSensorsProtocol::* const HandleSvc[])( + const DaemonSocketPDUHeader&, DaemonSocketPDU&, + DaemonSocketResultHandler*) = { + [GonkSensorsRegistryModule::SERVICE_ID] = + &GonkSensorsProtocol::HandleRegistrySvc, + [GonkSensorsPollModule::SERVICE_ID] = + &GonkSensorsProtocol::HandlePollSvc + }; + + DaemonSocketPDUHeader header; + + if (NS_FAILED(UnpackPDU(aPDU, header))) { + return; + } + if (!(header.mService < MOZ_ARRAY_LENGTH(HandleSvc)) || + !HandleSvc[header.mService]) { + HAL_ERR("Sensors service %d unknown", header.mService); + return; + } + + RefPtr<DaemonSocketResultHandler> res = FetchResultHandler(header); + + (this->*(HandleSvc[header.mService]))(header, aPDU, res); +} + +void +GonkSensorsProtocol::StoreResultHandler(const DaemonSocketPDU& aPDU) +{ + MOZ_ASSERT(!NS_IsMainThread()); + + mResultHandlerQ.AppendElement(aPDU.GetResultHandler()); +} + +// +// GonkSensorsInterface +// + +GonkSensorsInterface* +GonkSensorsInterface::GetInstance() +{ + static GonkSensorsInterface* sGonkSensorsInterface; + + if (sGonkSensorsInterface) { + return sGonkSensorsInterface; + } + + sGonkSensorsInterface = new GonkSensorsInterface(); + + return sGonkSensorsInterface; +} + +void +GonkSensorsInterface::SetNotificationHandler( + GonkSensorsNotificationHandler* aNotificationHandler) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mNotificationHandler = aNotificationHandler; +} + +/* + * The connect procedure consists of several steps. + * + * (1) Start listening for the command channel's socket connection: We + * do this before anything else, so that we don't miss connection + * requests from the Sensors daemon. This step will create a listen + * socket. + * + * (2) Start the Sensors daemon: When the daemon starts up it will open + * a socket connection to Gecko and thus create the data channel. + * Gecko already opened the listen socket in step (1). Step (2) ends + * with the creation of the data channel. + * + * (3) Signal success to the caller. + * + * If any step fails, we roll-back the procedure and signal an error to the + * caller. + */ +void +GonkSensorsInterface::Connect(GonkSensorsNotificationHandler* aNotificationHandler, + GonkSensorsResultHandler* aRes) +{ +#define BASE_SOCKET_NAME "sensorsd" + static unsigned long POSTFIX_LENGTH = 16; + + // If we could not cleanup properly before and an old + // instance of the daemon is still running, we kill it + // here. + mozilla::hal::StopSystemService("sensorsd"); + + mNotificationHandler = aNotificationHandler; + + mResultHandlerQ.AppendElement(aRes); + + if (!mProtocol) { + mProtocol = MakeUnique<GonkSensorsProtocol>(); + } + + if (!mListenSocket) { + mListenSocket = new ListenSocket(this, LISTEN_SOCKET); + } + + // Init, step 1: Listen for data channel... */ + + if (!mDataSocket) { + mDataSocket = new DaemonSocket(mProtocol.get(), this, DATA_SOCKET); + } else if (mDataSocket->GetConnectionStatus() == SOCKET_CONNECTED) { + // Command channel should not be open; let's close it. + mDataSocket->Close(); + } + + // The listen socket's name is generated with a random postfix. This + // avoids naming collisions if we still have a listen socket from a + // previously failed cleanup. It also makes it hard for malicious + // external programs to capture the socket name or connect before + // the daemon can do so. If no random postfix can be generated, we + // simply use the base name as-is. + nsresult rv = DaemonSocketConnector::CreateRandomAddressString( + NS_LITERAL_CSTRING(BASE_SOCKET_NAME), POSTFIX_LENGTH, mListenSocketName); + if (NS_FAILED(rv)) { + mListenSocketName.AssignLiteral(BASE_SOCKET_NAME); + } + + rv = mListenSocket->Listen(new DaemonSocketConnector(mListenSocketName), + mDataSocket); + if (NS_FAILED(rv)) { + OnConnectError(DATA_SOCKET); + return; + } + + // The protocol implementation needs a data channel for + // sending commands to the daemon. We set it here, because + // this is the earliest time when it's available. + mProtocol->SetConnection(mDataSocket); +} + +/* + * Disconnecting is inverse to connecting. + * + * (1) Close data socket: We close the data channel and the daemon will + * will notice. Once we see the socket's disconnect, we continue with + * the cleanup. + * + * (2) Close listen socket: The listen socket is not active any longer + * and we simply close it. + * + * (3) Signal success to the caller. + * + * We don't have to stop the daemon explicitly. It will cleanup and quit + * after it noticed the closing of the data channel + * + * Rolling back half-completed cleanups is not possible. In the case of + * an error, we simply push forward and try to recover during the next + * initialization. + */ +void +GonkSensorsInterface::Disconnect(GonkSensorsResultHandler* aRes) +{ + mNotificationHandler = nullptr; + + // Cleanup, step 1: Close data channel + mDataSocket->Close(); + + mResultHandlerQ.AppendElement(aRes); +} + +GonkSensorsRegistryInterface* +GonkSensorsInterface::GetSensorsRegistryInterface() +{ + if (mRegistryInterface) { + return mRegistryInterface.get(); + } + + mRegistryInterface = MakeUnique<GonkSensorsRegistryInterface>(mProtocol.get()); + + return mRegistryInterface.get(); +} + +GonkSensorsPollInterface* +GonkSensorsInterface::GetSensorsPollInterface() +{ + if (mPollInterface) { + return mPollInterface.get(); + } + + mPollInterface = MakeUnique<GonkSensorsPollInterface>(mProtocol.get()); + + return mPollInterface.get(); +} + +GonkSensorsInterface::GonkSensorsInterface() + : mNotificationHandler(nullptr) +{ } + +GonkSensorsInterface::~GonkSensorsInterface() +{ } + +void +GonkSensorsInterface::DispatchError(GonkSensorsResultHandler* aRes, + SensorsError aError) +{ + DaemonResultRunnable1<GonkSensorsResultHandler, void, + SensorsError, SensorsError>::Dispatch( + aRes, &GonkSensorsResultHandler::OnError, + ConstantInitOp1<SensorsError>(aError)); +} + +void +GonkSensorsInterface::DispatchError( + GonkSensorsResultHandler* aRes, nsresult aRv) +{ + SensorsError error; + + if (NS_FAILED(Convert(aRv, error))) { + error = SENSORS_ERROR_FAIL; + } + DispatchError(aRes, error); +} + +// |DaemonSocketConsumer|, |ListenSocketConsumer| + +void +GonkSensorsInterface::OnConnectSuccess(int aIndex) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mResultHandlerQ.IsEmpty()); + + switch (aIndex) { + case LISTEN_SOCKET: { + // Init, step 2: Start Sensors daemon + nsCString args("-a "); + args.Append(mListenSocketName); + mozilla::hal::StartSystemService("sensorsd", args.get()); + } + break; + case DATA_SOCKET: + if (!mResultHandlerQ.IsEmpty()) { + // Init, step 3: Signal success + RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0); + mResultHandlerQ.RemoveElementAt(0); + if (res) { + res->Connect(); + } + } + break; + } +} + +void +GonkSensorsInterface::OnConnectError(int aIndex) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mResultHandlerQ.IsEmpty()); + + switch (aIndex) { + case DATA_SOCKET: + // Stop daemon and close listen socket + mozilla::hal::StopSystemService("sensorsd"); + mListenSocket->Close(); + // fall through + case LISTEN_SOCKET: + if (!mResultHandlerQ.IsEmpty()) { + // Signal error to caller + RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0); + mResultHandlerQ.RemoveElementAt(0); + if (res) { + DispatchError(res, SENSORS_ERROR_FAIL); + } + } + break; + } +} + +/* + * Disconnects can happend + * + * (a) during startup, + * (b) during regular service, or + * (c) during shutdown. + * + * For cases (a) and (c), |mResultHandlerQ| contains an element. For + * case (b) |mResultHandlerQ| will be empty. This distinguishes a crash in + * the daemon. The following procedure to recover from crashes consists of + * several steps for case (b). + * + * (1) Close listen socket. + * (2) Wait for all sockets to be disconnected and inform caller about + * the crash. + * (3) After all resources have been cleaned up, let the caller restart + * the daemon. + */ +void +GonkSensorsInterface::OnDisconnect(int aIndex) +{ + MOZ_ASSERT(NS_IsMainThread()); + + switch (aIndex) { + case DATA_SOCKET: + // Cleanup, step 2 (Recovery, step 1): Close listen socket + mListenSocket->Close(); + break; + case LISTEN_SOCKET: + // Cleanup, step 3: Signal success to caller + if (!mResultHandlerQ.IsEmpty()) { + RefPtr<GonkSensorsResultHandler> res = mResultHandlerQ.ElementAt(0); + mResultHandlerQ.RemoveElementAt(0); + if (res) { + res->Disconnect(); + } + } + break; + } + + /* For recovery make sure all sockets disconnected, in order to avoid + * the remaining disconnects interfere with the restart procedure. + */ + if (mNotificationHandler && mResultHandlerQ.IsEmpty()) { + if (mListenSocket->GetConnectionStatus() == SOCKET_DISCONNECTED && + mDataSocket->GetConnectionStatus() == SOCKET_DISCONNECTED) { + // Recovery, step 2: Notify the caller to prepare the restart procedure. + mNotificationHandler->BackendErrorNotification(true); + mNotificationHandler = nullptr; + } + } +} + +} // namespace hal +} // namespace mozilla diff --git a/hal/gonk/GonkSensorsInterface.h b/hal/gonk/GonkSensorsInterface.h new file mode 100644 index 000000000..6e356dc36 --- /dev/null +++ b/hal/gonk/GonkSensorsInterface.h @@ -0,0 +1,191 @@ +/* -*- 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/. */ + +/* + * The sensors interface gives you access to the low-level sensors code + * in a platform-independent manner. The interfaces in this file allow + * for starting an stopping the sensors driver. Specific functionality + * is implemented in sub-interfaces. + */ + +#ifndef hal_gonk_GonkSensorsInterface_h +#define hal_gonk_GonkSensorsInterface_h + +#include <mozilla/ipc/DaemonSocketConsumer.h> +#include <mozilla/ipc/DaemonSocketMessageHandlers.h> +#include <mozilla/ipc/ListenSocketConsumer.h> +#include <mozilla/UniquePtr.h> +#include "SensorsTypes.h" + +namespace mozilla { +namespace ipc { + +class DaemonSocket; +class ListenSocket; + +} +} + +namespace mozilla { +namespace hal { + +class GonkSensorsPollInterface; +class GonkSensorsProtocol; +class GonkSensorsRegistryInterface; + +/** + * This class is the result-handler interface for the Sensors + * interface. Methods always run on the main thread. + */ +class GonkSensorsResultHandler + : public mozilla::ipc::DaemonSocketResultHandler +{ +public: + + /** + * Called if a command failed. + * + * @param aError The error code. + */ + virtual void OnError(SensorsError aError); + + /** + * The callback method for |GonkSensorsInterface::Connect|. + */ + virtual void Connect(); + + /** + * The callback method for |GonkSensorsInterface::Connect|. + */ + virtual void Disconnect(); + +protected: + virtual ~GonkSensorsResultHandler(); +}; + +/** + * This is the notification-handler interface. Implement this classes + * methods to handle event and notifications from the sensors daemon. + * All methods run on the main thread. + */ +class GonkSensorsNotificationHandler +{ +public: + + /** + * This notification is called when the backend code fails + * unexpectedly. Save state in the high-level code and restart + * the driver. + * + * @param aCrash True is the sensors driver crashed. + */ + virtual void BackendErrorNotification(bool aCrashed); + +protected: + virtual ~GonkSensorsNotificationHandler(); +}; + +/** + * This class implements the public interface to the Sensors functionality + * and driver. Use |GonkSensorsInterface::GetInstance| to retrieve an instance. + * All methods run on the main thread. + */ +class GonkSensorsInterface final + : public mozilla::ipc::DaemonSocketConsumer + , public mozilla::ipc::ListenSocketConsumer +{ +public: + /** + * Returns an instance of the Sensors backend. This code can return + * |nullptr| if no Sensors backend is available. + * + * @return An instance of |GonkSensorsInterface|. + */ + static GonkSensorsInterface* GetInstance(); + + /** + * This method sets the notification handler for sensor notifications. Call + * this method immediately after retreiving an instance of the class, or you + * won't be able able to receive notifications. You may not free the handler + * class while the Sensors backend is connected. + * + * @param aNotificationHandler An instance of a notification handler. + */ + void SetNotificationHandler( + GonkSensorsNotificationHandler* aNotificationHandler); + + /** + * This method starts the Sensors backend and establishes ad connection + * with Gecko. This is a multi-step process and errors are signalled by + * |GonkSensorsNotificationHandler::BackendErrorNotification|. If you see + * this notification before the connection has been established, it's + * certainly best to assume the Sensors backend to be not evailable. + * + * @param aRes The result handler. + */ + void Connect(GonkSensorsNotificationHandler* aNotificationHandler, + GonkSensorsResultHandler* aRes); + + /** + * This method disconnects Gecko from the Sensors backend and frees + * the backend's resources. This will invalidate all interfaces and + * state. Don't use any sensors functionality without reconnecting + * first. + * + * @param aRes The result handler. + */ + void Disconnect(GonkSensorsResultHandler* aRes); + + /** + * Returns the Registry interface for the connected Sensors backend. + * + * @return An instance of the Sensors Registry interface. + */ + GonkSensorsRegistryInterface* GetSensorsRegistryInterface(); + + /** + * Returns the Poll interface for the connected Sensors backend. + * + * @return An instance of the Sensors Poll interface. + */ + GonkSensorsPollInterface* GetSensorsPollInterface(); + +private: + enum Channel { + LISTEN_SOCKET, + DATA_SOCKET + }; + + GonkSensorsInterface(); + ~GonkSensorsInterface(); + + void DispatchError(GonkSensorsResultHandler* aRes, SensorsError aError); + void DispatchError(GonkSensorsResultHandler* aRes, nsresult aRv); + + // Methods for |DaemonSocketConsumer| and |ListenSocketConsumer| + // + + void OnConnectSuccess(int aIndex) override; + void OnConnectError(int aIndex) override; + void OnDisconnect(int aIndex) override; + + nsCString mListenSocketName; + RefPtr<mozilla::ipc::ListenSocket> mListenSocket; + RefPtr<mozilla::ipc::DaemonSocket> mDataSocket; + UniquePtr<GonkSensorsProtocol> mProtocol; + + nsTArray<RefPtr<GonkSensorsResultHandler> > mResultHandlerQ; + + GonkSensorsNotificationHandler* mNotificationHandler; + + UniquePtr<GonkSensorsRegistryInterface> mRegistryInterface; + UniquePtr<GonkSensorsPollInterface> mPollInterface; +}; + +} // namespace hal +} // namespace mozilla + +#endif // hal_gonk_GonkSensorsInterface_h diff --git a/hal/gonk/GonkSensorsPollInterface.cpp b/hal/gonk/GonkSensorsPollInterface.cpp new file mode 100644 index 000000000..d4edc2e7a --- /dev/null +++ b/hal/gonk/GonkSensorsPollInterface.cpp @@ -0,0 +1,431 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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 "GonkSensorsPollInterface.h" +#include "HalLog.h" +#include <mozilla/UniquePtr.h> + +namespace mozilla { +namespace hal { + +using namespace mozilla::ipc; + +// +// GonkSensorsPollResultHandler +// + +void +GonkSensorsPollResultHandler::OnError(SensorsError aError) +{ + HAL_ERR("Received error code %d", static_cast<int>(aError)); +} + +void +GonkSensorsPollResultHandler::EnableSensor() +{ } + +void +GonkSensorsPollResultHandler::DisableSensor() +{ } + +void +GonkSensorsPollResultHandler::SetPeriod() +{ } + +GonkSensorsPollResultHandler::~GonkSensorsPollResultHandler() +{ } + +// +// GonkSensorsPollNotificationHandler +// + +void +GonkSensorsPollNotificationHandler::ErrorNotification(SensorsError aError) +{ + HAL_ERR("Received error code %d", static_cast<int>(aError)); +} + +void +GonkSensorsPollNotificationHandler::SensorDetectedNotification( + int32_t aId, + SensorsType aType, + float aRange, + float aResolution, + float aPower, + int32_t aMinPeriod, + int32_t aMaxPeriod, + SensorsTriggerMode aTriggerMode, + SensorsDeliveryMode aDeliveryMode) +{ } + +void +GonkSensorsPollNotificationHandler::SensorLostNotification(int32_t aId) +{ } + +void +GonkSensorsPollNotificationHandler::EventNotification(int32_t aId, + const SensorsEvent& aEvent) +{ } + +GonkSensorsPollNotificationHandler::~GonkSensorsPollNotificationHandler() +{ } + +// +// GonkSensorsPollModule +// + +GonkSensorsPollModule::GonkSensorsPollModule() + : mProtocolVersion(0) +{ } + +GonkSensorsPollModule::~GonkSensorsPollModule() +{ } + +nsresult +GonkSensorsPollModule::SetProtocolVersion(unsigned long aProtocolVersion) +{ + if ((aProtocolVersion < MIN_PROTOCOL_VERSION) || + (aProtocolVersion > MAX_PROTOCOL_VERSION)) { + HAL_ERR("Sensors Poll protocol version %lu not supported", + aProtocolVersion); + return NS_ERROR_ILLEGAL_VALUE; + } + mProtocolVersion = aProtocolVersion; + return NS_OK; +} + +void +GonkSensorsPollModule::HandleSvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + static void (GonkSensorsPollModule::* const HandleOp[])( + const DaemonSocketPDUHeader&, DaemonSocketPDU&, + DaemonSocketResultHandler*) = { + [0] = &GonkSensorsPollModule::HandleRsp, + [1] = &GonkSensorsPollModule::HandleNtf + }; + + MOZ_ASSERT(!NS_IsMainThread()); // I/O thread + + // Negate twice to map bit to 0/1 + unsigned long isNtf = !!(aHeader.mOpcode & 0x80); + + (this->*(HandleOp[isNtf]))(aHeader, aPDU, aRes); +} + +// Commands +// + +nsresult +GonkSensorsPollModule::EnableSensorCmd(int32_t aId, GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<DaemonSocketPDU> pdu = + MakeUnique<DaemonSocketPDU>(SERVICE_ID, OPCODE_ENABLE_SENSOR, 0); + + nsresult rv = PackPDU(aId, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = Send(pdu.get(), aRes); + if (NS_FAILED(rv)) { + return rv; + } + Unused << pdu.release(); + return NS_OK; +} + +nsresult +GonkSensorsPollModule::DisableSensorCmd(int32_t aId, GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<DaemonSocketPDU> pdu = + MakeUnique<DaemonSocketPDU>(SERVICE_ID, OPCODE_DISABLE_SENSOR, 0); + + nsresult rv = PackPDU(aId, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = Send(pdu.get(), aRes); + if (NS_FAILED(rv)) { + return rv; + } + Unused << pdu.release(); + return NS_OK; +} + +nsresult +GonkSensorsPollModule::SetPeriodCmd(int32_t aId, uint64_t aPeriod, + GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<DaemonSocketPDU> pdu = + MakeUnique<DaemonSocketPDU>(SERVICE_ID, OPCODE_SET_PERIOD, 0); + + nsresult rv = PackPDU(aId, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = PackPDU(aPeriod, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = Send(pdu.get(), aRes); + if (NS_FAILED(rv)) { + return rv; + } + Unused << pdu.release(); + return NS_OK; +} + +// Responses +// + +void +GonkSensorsPollModule::ErrorRsp( + const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, GonkSensorsPollResultHandler* aRes) +{ + ErrorRunnable::Dispatch( + aRes, &GonkSensorsPollResultHandler::OnError, UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::EnableSensorRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes) +{ + ResultRunnable::Dispatch( + aRes, &GonkSensorsPollResultHandler::EnableSensor, UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::DisableSensorRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes) +{ + ResultRunnable::Dispatch( + aRes, &GonkSensorsPollResultHandler::DisableSensor, UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::SetPeriodRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes) +{ + ResultRunnable::Dispatch( + aRes, &GonkSensorsPollResultHandler::SetPeriod, UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::HandleRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + static void (GonkSensorsPollModule::* const sHandleRsp[])( + const DaemonSocketPDUHeader&, DaemonSocketPDU&, + GonkSensorsPollResultHandler*) = { + [OPCODE_ERROR] = &GonkSensorsPollModule::ErrorRsp, + [OPCODE_ENABLE_SENSOR] = &GonkSensorsPollModule::EnableSensorRsp, + [OPCODE_DISABLE_SENSOR] = &GonkSensorsPollModule::DisableSensorRsp, + [OPCODE_SET_PERIOD] = &GonkSensorsPollModule::SetPeriodRsp, + }; + + MOZ_ASSERT(!NS_IsMainThread()); // I/O thread + + if (!(aHeader.mOpcode < MOZ_ARRAY_LENGTH(sHandleRsp)) || + !sHandleRsp[aHeader.mOpcode]) { + HAL_ERR("Sensors poll response opcode %d unknown", aHeader.mOpcode); + return; + } + + RefPtr<GonkSensorsPollResultHandler> res = + static_cast<GonkSensorsPollResultHandler*>(aRes); + + if (!res) { + return; // Return early if no result handler has been set for response + } + + (this->*(sHandleRsp[aHeader.mOpcode]))(aHeader, aPDU, res); +} + +// Notifications +// + +// Returns the current notification handler to a notification runnable +class GonkSensorsPollModule::NotificationHandlerWrapper final +{ +public: + typedef GonkSensorsPollNotificationHandler ObjectType; + + static ObjectType* GetInstance() + { + MOZ_ASSERT(NS_IsMainThread()); + + return sNotificationHandler; + } + + static GonkSensorsPollNotificationHandler* sNotificationHandler; +}; + +GonkSensorsPollNotificationHandler* + GonkSensorsPollModule::NotificationHandlerWrapper::sNotificationHandler; + +void +GonkSensorsPollModule::ErrorNtf( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU) +{ + ErrorNotification::Dispatch( + &GonkSensorsPollNotificationHandler::ErrorNotification, + UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::SensorDetectedNtf( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU) +{ + SensorDetectedNotification::Dispatch( + &GonkSensorsPollNotificationHandler::SensorDetectedNotification, + UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::SensorLostNtf( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU) +{ + SensorLostNotification::Dispatch( + &GonkSensorsPollNotificationHandler::SensorLostNotification, + UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::EventNtf( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU) +{ + EventNotification::Dispatch( + &GonkSensorsPollNotificationHandler::EventNotification, + UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsPollModule::HandleNtf( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + static void (GonkSensorsPollModule::* const sHandleNtf[])( + const DaemonSocketPDUHeader&, DaemonSocketPDU&) = { + [0] = &GonkSensorsPollModule::ErrorNtf, + [1] = &GonkSensorsPollModule::SensorDetectedNtf, + [2] = &GonkSensorsPollModule::SensorLostNtf, + [3] = &GonkSensorsPollModule::EventNtf + }; + + MOZ_ASSERT(!NS_IsMainThread()); + + uint8_t index = aHeader.mOpcode - 0x80; + + if (!(index < MOZ_ARRAY_LENGTH(sHandleNtf)) || !sHandleNtf[index]) { + HAL_ERR("Sensors poll notification opcode %d unknown", aHeader.mOpcode); + return; + } + + (this->*(sHandleNtf[index]))(aHeader, aPDU); +} + +// +// GonkSensorsPollInterface +// + +GonkSensorsPollInterface::GonkSensorsPollInterface( + GonkSensorsPollModule* aModule) + : mModule(aModule) +{ } + +GonkSensorsPollInterface::~GonkSensorsPollInterface() +{ } + +void +GonkSensorsPollInterface::SetNotificationHandler( + GonkSensorsPollNotificationHandler* aNotificationHandler) +{ + MOZ_ASSERT(NS_IsMainThread()); + + GonkSensorsPollModule::NotificationHandlerWrapper::sNotificationHandler = + aNotificationHandler; +} + +nsresult +GonkSensorsPollInterface::SetProtocolVersion(unsigned long aProtocolVersion) +{ + MOZ_ASSERT(mModule); + + return mModule->SetProtocolVersion(aProtocolVersion); +} + +void +GonkSensorsPollInterface::EnableSensor(int32_t aId, + GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(mModule); + + nsresult rv = mModule->EnableSensorCmd(aId, aRes); + if (NS_FAILED(rv)) { + DispatchError(aRes, rv); + } +} + +void +GonkSensorsPollInterface::DisableSensor(int32_t aId, + GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(mModule); + + nsresult rv = mModule->DisableSensorCmd(aId, aRes); + if (NS_FAILED(rv)) { + DispatchError(aRes, rv); + } +} + +void +GonkSensorsPollInterface::SetPeriod(int32_t aId, uint64_t aPeriod, + GonkSensorsPollResultHandler* aRes) +{ + MOZ_ASSERT(mModule); + + nsresult rv = mModule->SetPeriodCmd(aId, aPeriod, aRes); + if (NS_FAILED(rv)) { + DispatchError(aRes, rv); + } +} + +void +GonkSensorsPollInterface::DispatchError( + GonkSensorsPollResultHandler* aRes, SensorsError aError) +{ + DaemonResultRunnable1<GonkSensorsPollResultHandler, void, + SensorsError, SensorsError>::Dispatch( + aRes, &GonkSensorsPollResultHandler::OnError, + ConstantInitOp1<SensorsError>(aError)); +} + +void +GonkSensorsPollInterface::DispatchError( + GonkSensorsPollResultHandler* aRes, nsresult aRv) +{ + SensorsError error; + + if (NS_FAILED(Convert(aRv, error))) { + error = SENSORS_ERROR_FAIL; + } + DispatchError(aRes, error); +} + +} // namespace hal +} // namespace mozilla diff --git a/hal/gonk/GonkSensorsPollInterface.h b/hal/gonk/GonkSensorsPollInterface.h new file mode 100644 index 000000000..89381a9bd --- /dev/null +++ b/hal/gonk/GonkSensorsPollInterface.h @@ -0,0 +1,340 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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/. */ + +/* + * The poll interface gives yo access to the Sensors daemon's Poll service, + * which handles sensors. The poll service will inform you when sensors are + * detected or removed from the system. You can activate (or deactivate) + * existing sensors and poll will deliver the sensors' events. + * + * All public methods and callback methods run on the main thread. + */ + +#ifndef hal_gonk_GonkSensorsPollInterface_h +#define hal_gonk_GonkSensorsPollInterface_h + +#include <mozilla/ipc/DaemonRunnables.h> +#include <mozilla/ipc/DaemonSocketMessageHandlers.h> +#include "SensorsTypes.h" + +namespace mozilla { +namespace ipc { + +class DaemonSocketPDU; +class DaemonSocketPDUHeader; + +} +} + +namespace mozilla { +namespace hal { + +class SensorsInterface; + +using mozilla::ipc::DaemonSocketPDU; +using mozilla::ipc::DaemonSocketPDUHeader; +using mozilla::ipc::DaemonSocketResultHandler; + +/** + * This class is the result-handler interface for the Sensors + * Poll interface. Methods always run on the main thread. + */ +class GonkSensorsPollResultHandler : public DaemonSocketResultHandler +{ +public: + + /** + * Called if a poll command failed. + * + * @param aError The error code. + */ + virtual void OnError(SensorsError aError); + + /** + * The callback method for |GonkSensorsPollInterface::EnableSensor|. + */ + virtual void EnableSensor(); + + /** + * The callback method for |GonkSensorsPollInterface::DisableSensor|. + */ + virtual void DisableSensor(); + + /** + * The callback method for |GonkSensorsPollInterface::SetPeriod|. + */ + virtual void SetPeriod(); + +protected: + virtual ~GonkSensorsPollResultHandler(); +}; + +/** + * This is the notification-handler interface. Implement this classes + * methods to handle event and notifications from the sensors daemon. + */ +class GonkSensorsPollNotificationHandler +{ +public: + + /** + * The notification handler for errors. You'll receive this call if + * there's been a critical error in the daemon. Either try to handle + * the error, or restart the daemon. + * + * @param aError The error code. + */ + virtual void ErrorNotification(SensorsError aError); + + /** + * This methods gets call when a new sensor has been detected. + * + * @param aId The sensor's id. + * @param aType The sensor's type. + * @param aRange The sensor's maximum value. + * @param aResolution The minimum difference between two consecutive values. + * @param aPower The sensor's power consumption (in mA). + * @param aMinPeriod The minimum time between two events (in ns). + * @param aMaxPeriod The maximum time between two events (in ns). + * @param aTriggerMode The sensor's mode for triggering events. + * @param aDeliveryMode The sensor's urgency for event delivery. + */ + virtual void SensorDetectedNotification(int32_t aId, SensorsType aType, + float aRange, float aResolution, + float aPower, int32_t aMinPeriod, + int32_t aMaxPeriod, + SensorsTriggerMode aTriggerMode, + SensorsDeliveryMode aDeliveryMode); + + /** + * This methods gets call when an existing sensor has been removed. + * + * @param aId The sensor's id. + */ + virtual void SensorLostNotification(int32_t aId); + + /** + * This is the callback methods for sensor events. Only activated sensors + * generate events. All sensors are disabled by default. The actual data + * of the event depends on the sensor type. + * + * @param aId The sensor's id. + * @param aEvent The event's data. + */ + virtual void EventNotification(int32_t aId, const SensorsEvent& aEvent); + +protected: + virtual ~GonkSensorsPollNotificationHandler(); +}; + +/** + * This is the module class for the Sensors poll component. It handles PDU + * packing and unpacking. Methods are either executed on the main thread or + * the I/O thread. + * + * This is an internal class, use |GonkSensorsPollInterface| instead. + */ +class GonkSensorsPollModule +{ +public: + class NotificationHandlerWrapper; + + enum { + SERVICE_ID = 0x01 + }; + + enum { + OPCODE_ERROR = 0x00, + OPCODE_ENABLE_SENSOR = 0x01, + OPCODE_DISABLE_SENSOR = 0x02, + OPCODE_SET_PERIOD = 0x03 + }; + + enum { + MIN_PROTOCOL_VERSION = 1, + MAX_PROTOCOL_VERSION = 1 + }; + + virtual nsresult Send(DaemonSocketPDU* aPDU, + DaemonSocketResultHandler* aRes) = 0; + + nsresult SetProtocolVersion(unsigned long aProtocolVersion); + + // + // Commands + // + + nsresult EnableSensorCmd(int32_t aId, + GonkSensorsPollResultHandler* aRes); + + nsresult DisableSensorCmd(int32_t aId, + GonkSensorsPollResultHandler* aRes); + + nsresult SetPeriodCmd(int32_t aId, uint64_t aPeriod, + GonkSensorsPollResultHandler* aRes); + +protected: + GonkSensorsPollModule(); + virtual ~GonkSensorsPollModule(); + + void HandleSvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes); + +private: + + // + // Responses + // + + typedef mozilla::ipc::DaemonResultRunnable0< + GonkSensorsPollResultHandler, void> + ResultRunnable; + + typedef mozilla::ipc::DaemonResultRunnable1< + GonkSensorsPollResultHandler, void, SensorsError, SensorsError> + ErrorRunnable; + + void ErrorRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes); + + void EnableSensorRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes); + + void DisableSensorRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes); + + void SetPeriodRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsPollResultHandler* aRes); + + void HandleRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes); + + // + // Notifications + // + + typedef mozilla::ipc::DaemonNotificationRunnable1< + NotificationHandlerWrapper, void, SensorsError> + ErrorNotification; + + typedef mozilla::ipc::DaemonNotificationRunnable9< + NotificationHandlerWrapper, void, int32_t, SensorsType, + float, float, float, int32_t, int32_t, SensorsTriggerMode, + SensorsDeliveryMode> + SensorDetectedNotification; + + typedef mozilla::ipc::DaemonNotificationRunnable1< + NotificationHandlerWrapper, void, int32_t> + SensorLostNotification; + + typedef mozilla::ipc::DaemonNotificationRunnable2< + NotificationHandlerWrapper, void, int32_t, SensorsEvent, int32_t, + const SensorsEvent&> + EventNotification; + + class SensorDetectedInitOp; + class SensorLostInitOp; + class EventInitOp; + + void ErrorNtf(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU); + + void SensorDetectedNtf(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU); + + void SensorLostNtf(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU); + + void EventNtf(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU); + + void HandleNtf(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes); + +private: + unsigned long mProtocolVersion; +}; + +/** + * This class implements the public interface to the Sensors poll + * component. Use |SensorsInterface::GetPollInterface| to retrieve + * an instance. All methods run on the main thread. + */ +class GonkSensorsPollInterface final +{ +public: + GonkSensorsPollInterface(GonkSensorsPollModule* aModule); + ~GonkSensorsPollInterface(); + + /** + * This method sets the notification handler for poll notifications. Call + * this method immediately after registering the module. Otherwise you won't + * be able able to receive poll notifications. You may not free the handler + * class while the poll component is regsitered. + * + * @param aNotificationHandler An instance of a poll notification handler. + */ + void SetNotificationHandler( + GonkSensorsPollNotificationHandler* aNotificationHandler); + + /** + * This method sets the protocol version. You should set it to the + * value that has been returned from the backend when registering the + * Poll service. You cannot send or receive messages before setting + * the protocol version. + * + * @param aProtocolVersion + * @return NS_OK for supported versions, or an XPCOM error code otherwise. + */ + nsresult SetProtocolVersion(unsigned long aProtocolVersion); + + /** + * Enables an existing sensor. The sensor id will have been delivered in + * a SensorDetectedNotification. + * + * @param aId The sensor's id. + * @param aRes The result handler. + */ + void EnableSensor(int32_t aId, GonkSensorsPollResultHandler* aRes); + + /** + * Disables an existing sensor. The sensor id will have been delivered in + * a SensorDetectedNotification. + * + * @param aId The sensor's id. + * @param aRes The result handler. + */ + void DisableSensor(int32_t aId, GonkSensorsPollResultHandler* aRes); + + /** + * Sets the period for a sensor. The sensor id will have been delivered in + * a SensorDetectedNotification. The value for the period should be between + * the sensor's minimum and maximum period. + * + * @param aId The sensor's id. + * @param aPeriod The sensor's new period. + * @param aRes The result handler. + */ + void SetPeriod(int32_t aId, uint64_t aPeriod, GonkSensorsPollResultHandler* aRes); + +private: + void DispatchError(GonkSensorsPollResultHandler* aRes, SensorsError aError); + void DispatchError(GonkSensorsPollResultHandler* aRes, nsresult aRv); + + GonkSensorsPollModule* mModule; +}; + +} // hal +} // namespace mozilla + +#endif // hal_gonk_GonkSensorsPollInterface_h diff --git a/hal/gonk/GonkSensorsRegistryInterface.cpp b/hal/gonk/GonkSensorsRegistryInterface.cpp new file mode 100644 index 000000000..601dc7a2a --- /dev/null +++ b/hal/gonk/GonkSensorsRegistryInterface.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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 "GonkSensorsRegistryInterface.h" +#include "GonkSensorsHelpers.h" +#include "HalLog.h" +#include <mozilla/UniquePtr.h> + +namespace mozilla { +namespace hal { + +using namespace mozilla::ipc; + +// +// GonkSensorsRegistryResultHandler +// + +void +GonkSensorsRegistryResultHandler::OnError(SensorsError aError) +{ + HAL_ERR("Received error code %d", static_cast<int>(aError)); +} + +void +GonkSensorsRegistryResultHandler::RegisterModule(uint32_t aProtocolVersion) +{ } + +void +GonkSensorsRegistryResultHandler::UnregisterModule() +{ } + +GonkSensorsRegistryResultHandler::~GonkSensorsRegistryResultHandler() +{ } + +// +// GonkSensorsRegistryModule +// + +GonkSensorsRegistryModule::~GonkSensorsRegistryModule() +{ } + +void +GonkSensorsRegistryModule::HandleSvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + DaemonSocketResultHandler* aRes) +{ + static void (GonkSensorsRegistryModule::* const HandleRsp[])( + const DaemonSocketPDUHeader&, + DaemonSocketPDU&, + GonkSensorsRegistryResultHandler*) = { + [OPCODE_ERROR] = &GonkSensorsRegistryModule::ErrorRsp, + [OPCODE_REGISTER_MODULE] = &GonkSensorsRegistryModule::RegisterModuleRsp, + [OPCODE_UNREGISTER_MODULE] = &GonkSensorsRegistryModule::UnregisterModuleRsp + }; + + if ((aHeader.mOpcode >= MOZ_ARRAY_LENGTH(HandleRsp)) || + !HandleRsp[aHeader.mOpcode]) { + HAL_ERR("Sensors registry response opcode %d unknown", aHeader.mOpcode); + return; + } + + RefPtr<GonkSensorsRegistryResultHandler> res = + static_cast<GonkSensorsRegistryResultHandler*>(aRes); + + if (!res) { + return; // Return early if no result handler has been set + } + + (this->*(HandleRsp[aHeader.mOpcode]))(aHeader, aPDU, res); +} + +// Commands +// + +nsresult +GonkSensorsRegistryModule::RegisterModuleCmd( + uint8_t aId, GonkSensorsRegistryResultHandler* aRes) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<DaemonSocketPDU> pdu = + MakeUnique<DaemonSocketPDU>(SERVICE_ID, OPCODE_REGISTER_MODULE, 0); + + nsresult rv = PackPDU(aId, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = Send(pdu.get(), aRes); + if (NS_FAILED(rv)) { + return rv; + } + Unused << pdu.release(); + return NS_OK; +} + +nsresult +GonkSensorsRegistryModule::UnregisterModuleCmd( + uint8_t aId, GonkSensorsRegistryResultHandler* aRes) +{ + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr<DaemonSocketPDU> pdu = + MakeUnique<DaemonSocketPDU>(SERVICE_ID, OPCODE_UNREGISTER_MODULE, 0); + + nsresult rv = PackPDU(aId, *pdu); + if (NS_FAILED(rv)) { + return rv; + } + rv = Send(pdu.get(), aRes); + if (NS_FAILED(rv)) { + return rv; + } + Unused << pdu.release(); + return NS_OK; +} + +// Responses +// + +void +GonkSensorsRegistryModule::ErrorRsp( + const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, GonkSensorsRegistryResultHandler* aRes) +{ + ErrorRunnable::Dispatch( + aRes, &GonkSensorsRegistryResultHandler::OnError, UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsRegistryModule::RegisterModuleRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + GonkSensorsRegistryResultHandler* aRes) +{ + Uint32ResultRunnable::Dispatch( + aRes, + &GonkSensorsRegistryResultHandler::RegisterModule, + UnpackPDUInitOp(aPDU)); +} + +void +GonkSensorsRegistryModule::UnregisterModuleRsp( + const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, + GonkSensorsRegistryResultHandler* aRes) +{ + ResultRunnable::Dispatch( + aRes, + &GonkSensorsRegistryResultHandler::UnregisterModule, + UnpackPDUInitOp(aPDU)); +} + +// +// GonkSensorsRegistryInterface +// + +GonkSensorsRegistryInterface::GonkSensorsRegistryInterface( + GonkSensorsRegistryModule* aModule) + : mModule(aModule) +{ } + +GonkSensorsRegistryInterface::~GonkSensorsRegistryInterface() +{ } + +void +GonkSensorsRegistryInterface::RegisterModule( + uint8_t aId, GonkSensorsRegistryResultHandler* aRes) +{ + MOZ_ASSERT(mModule); + + nsresult rv = mModule->RegisterModuleCmd(aId, aRes); + if (NS_FAILED(rv)) { + DispatchError(aRes, rv); + } +} + +void +GonkSensorsRegistryInterface::UnregisterModule( + uint8_t aId, GonkSensorsRegistryResultHandler* aRes) +{ + MOZ_ASSERT(mModule); + + nsresult rv = mModule->UnregisterModuleCmd(aId, aRes); + if (NS_FAILED(rv)) { + DispatchError(aRes, rv); + } +} + +void +GonkSensorsRegistryInterface::DispatchError( + GonkSensorsRegistryResultHandler* aRes, SensorsError aError) +{ + DaemonResultRunnable1<GonkSensorsRegistryResultHandler, void, + SensorsError, SensorsError>::Dispatch( + aRes, &GonkSensorsRegistryResultHandler::OnError, + ConstantInitOp1<SensorsError>(aError)); +} + +void +GonkSensorsRegistryInterface::DispatchError( + GonkSensorsRegistryResultHandler* aRes, nsresult aRv) +{ + SensorsError error; + + if (NS_FAILED(Convert(aRv, error))) { + error = SENSORS_ERROR_FAIL; + } + DispatchError(aRes, error); +} + +} // namespace hal +} // namespace mozilla diff --git a/hal/gonk/GonkSensorsRegistryInterface.h b/hal/gonk/GonkSensorsRegistryInterface.h new file mode 100644 index 000000000..a9d98d653 --- /dev/null +++ b/hal/gonk/GonkSensorsRegistryInterface.h @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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/. */ + +/* + * The registry interface gives yo access to the Sensors daemon's Registry + * service. The purpose of the service is to register and setup all other + * services, and make them available. + * + * All public methods and callback methods run on the main thread. + */ + +#ifndef hal_gonk_GonkSensorsRegistryInterface_h +#define hal_gonk_GonkSensorsRegistryInterface_h + +#include <mozilla/ipc/DaemonRunnables.h> +#include <mozilla/ipc/DaemonSocketMessageHandlers.h> +#include "SensorsTypes.h" + +namespace mozilla { +namespace ipc { + +class DaemonSocketPDU; +class DaemonSocketPDUHeader; + +} +} + +namespace mozilla { +namespace hal { + +class SensorsInterface; + +using mozilla::ipc::DaemonSocketPDU; +using mozilla::ipc::DaemonSocketPDUHeader; +using mozilla::ipc::DaemonSocketResultHandler; + +/** + * This class is the result-handler interface for the Sensors + * Registry interface. Methods always run on the main thread. + */ +class GonkSensorsRegistryResultHandler : public DaemonSocketResultHandler +{ +public: + + /** + * Called if a registry command failed. + * + * @param aError The error code. + */ + virtual void OnError(SensorsError aError); + + /** + * The callback method for |GonkSensorsRegistryInterface::RegisterModule|. + * + * @param aProtocolVersion The daemon's protocol version. Make sure it's + * compatible with Gecko's implementation. + */ + virtual void RegisterModule(uint32_t aProtocolVersion); + + /** + * The callback method for |SensorsRegsitryInterface::UnregisterModule|. + */ + virtual void UnregisterModule(); + +protected: + virtual ~GonkSensorsRegistryResultHandler(); +}; + +/** + * This is the module class for the Sensors registry component. It handles + * PDU packing and unpacking. Methods are either executed on the main thread + * or the I/O thread. + * + * This is an internal class, use |GonkSensorsRegistryInterface| instead. + */ +class GonkSensorsRegistryModule +{ +public: + enum { + SERVICE_ID = 0x00 + }; + + enum { + OPCODE_ERROR = 0x00, + OPCODE_REGISTER_MODULE = 0x01, + OPCODE_UNREGISTER_MODULE = 0x02 + }; + + virtual nsresult Send(DaemonSocketPDU* aPDU, + DaemonSocketResultHandler* aRes) = 0; + + // + // Commands + // + + nsresult RegisterModuleCmd(uint8_t aId, + GonkSensorsRegistryResultHandler* aRes); + + nsresult UnregisterModuleCmd(uint8_t aId, + GonkSensorsRegistryResultHandler* aRes); + +protected: + virtual ~GonkSensorsRegistryModule(); + + void HandleSvc(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, DaemonSocketResultHandler* aRes); + + // + // Responses + // + + typedef mozilla::ipc::DaemonResultRunnable0< + GonkSensorsRegistryResultHandler, void> + ResultRunnable; + + typedef mozilla::ipc::DaemonResultRunnable1< + GonkSensorsRegistryResultHandler, void, uint32_t, uint32_t> + Uint32ResultRunnable; + + typedef mozilla::ipc::DaemonResultRunnable1< + GonkSensorsRegistryResultHandler, void, SensorsError, SensorsError> + ErrorRunnable; + + void ErrorRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsRegistryResultHandler* aRes); + + void RegisterModuleRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsRegistryResultHandler* aRes); + + void UnregisterModuleRsp(const DaemonSocketPDUHeader& aHeader, + DaemonSocketPDU& aPDU, + GonkSensorsRegistryResultHandler* aRes); +}; + +/** + * This class implements the public interface to the Sensors Registry + * component. Use |SensorsInterface::GetRegistryInterface| to retrieve + * an instance. All methods run on the main thread. + */ +class GonkSensorsRegistryInterface final +{ +public: + GonkSensorsRegistryInterface(GonkSensorsRegistryModule* aModule); + ~GonkSensorsRegistryInterface(); + + /** + * Sends a RegisterModule command to the Sensors daemon. When the + * result handler's |RegisterModule| method gets called, the service + * has been registered successfully and can be used. + * + * @param aId The id of the service that is to be registered. + * @param aRes The result handler. + */ + void RegisterModule(uint8_t aId, GonkSensorsRegistryResultHandler* aRes); + + /** + * Sends an UnregisterModule command to the Sensors daemon. The service + * should not be used afterwards until it has been registered again. + * + * @param aId The id of the service that is to be unregistered. + * @param aRes The result handler. + */ + void UnregisterModule(uint8_t aId, GonkSensorsRegistryResultHandler* aRes); + +private: + void DispatchError(GonkSensorsRegistryResultHandler* aRes, + SensorsError aError); + void DispatchError(GonkSensorsRegistryResultHandler* aRes, + nsresult aRv); + + GonkSensorsRegistryModule* mModule; +}; + +} // namespace hal +} // namespace mozilla + +#endif // hal_gonk_GonkSensorsRegistryInterface_h diff --git a/hal/gonk/GonkSwitch.cpp b/hal/gonk/GonkSwitch.cpp new file mode 100644 index 000000000..b2c31c973 --- /dev/null +++ b/hal/gonk/GonkSwitch.cpp @@ -0,0 +1,479 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright 2012 Mozilla Foundation and Mozilla contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fcntl.h> +#include <sysutils/NetlinkEvent.h> + +#include "base/message_loop.h" +#include "base/task.h" + +#include "Hal.h" +#include "HalLog.h" +#include "mozilla/FileUtils.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Monitor.h" +#include "nsPrintfCString.h" +#include "nsXULAppAPI.h" +#include "nsThreadUtils.h" +#include "UeventPoller.h" + +using namespace mozilla::hal; + +#define SWITCH_HEADSET_DEVPATH "/devices/virtual/switch/h2w" +#define SWITCH_USB_DEVPATH_GB "/devices/virtual/switch/usb_configuration" +#define SWITCH_USB_DEVPATH_ICS "/devices/virtual/android_usb/android0" + +namespace mozilla { +namespace hal_impl { +/** + * The uevent for a usb on GB insertion looks like: + * + * change@/devices/virtual/switch/usb_configuration + * ACTION=change + * DEVPATH=/devices/virtual/switch/usb_configuration + * SUBSYSTEM=switch + * SWITCH_NAME=usb_configuration + * SWITCH_STATE=0 + * SEQNUM=5038 + */ +class SwitchHandler +{ +public: + NS_INLINE_DECL_REFCOUNTING(SwitchHandler) + + SwitchHandler(const char* aDevPath, SwitchDevice aDevice) + : mDevPath(aDevPath), + mState(SWITCH_STATE_UNKNOWN), + mDevice(aDevice) + { + GetInitialState(); + } + + bool CheckEvent(NetlinkEvent* aEvent) + { + if (strcmp(GetSubsystem(), aEvent->getSubsystem()) || + strcmp(mDevPath, aEvent->findParam("DEVPATH"))) { + return false; + } + + mState = ConvertState(GetStateString(aEvent)); + return mState != SWITCH_STATE_UNKNOWN; + } + + SwitchState GetState() + { + return mState; + } + + SwitchDevice GetType() + { + return mDevice; + } +protected: + virtual ~SwitchHandler() + { + } + + virtual const char* GetSubsystem() + { + return "switch"; + } + + virtual const char* GetStateString(NetlinkEvent* aEvent) + { + return aEvent->findParam("SWITCH_STATE"); + } + + void GetInitialState() + { + nsPrintfCString statePath("/sys%s/state", mDevPath); + int fd = open(statePath.get(), O_RDONLY); + if (fd <= 0) { + return; + } + + ScopedClose autoClose(fd); + char state[16]; + ssize_t bytesRead = read(fd, state, sizeof(state)); + if (bytesRead < 0) { + HAL_ERR("Read data from %s fails", statePath.get()); + return; + } + + if (state[bytesRead - 1] == '\n') { + bytesRead--; + } + + state[bytesRead] = '\0'; + mState = ConvertState(state); + } + + virtual SwitchState ConvertState(const char* aState) + { + MOZ_ASSERT(aState); + return aState[0] == '0' ? SWITCH_STATE_OFF : SWITCH_STATE_ON; + } + + const char* mDevPath; + SwitchState mState; + SwitchDevice mDevice; +}; + +/** + * The uevent delivered for the USB configuration under ICS looks like, + * + * change@/devices/virtual/android_usb/android0 + * ACTION=change + * DEVPATH=/devices/virtual/android_usb/android0 + * SUBSYSTEM=android_usb + * USB_STATE=CONFIGURED + * SEQNUM=1802 + */ +class SwitchHandlerUsbIcs: public SwitchHandler +{ +public: + SwitchHandlerUsbIcs(const char* aDevPath) : SwitchHandler(aDevPath, SWITCH_USB) + { + SwitchHandler::GetInitialState(); + } + + virtual ~SwitchHandlerUsbIcs() { } + +protected: + virtual const char* GetSubsystem() + { + return "android_usb"; + } + + virtual const char* GetStateString(NetlinkEvent* aEvent) + { + return aEvent->findParam("USB_STATE"); + } + + SwitchState ConvertState(const char* aState) + { + MOZ_ASSERT(aState); + return strcmp(aState, "CONFIGURED") == 0 ? SWITCH_STATE_ON : SWITCH_STATE_OFF; + } +}; + +/** + * The uevent delivered for the headset under ICS looks like, + * + * change@/devices/virtual/switch/h2w + * ACTION=change + * DEVPATH=/devices/virtual/switch/h2w + * SUBSYSTEM=switch + * SWITCH_NAME=h2w + * SWITCH_STATE=2 // Headset with no mic + * SEQNUM=2581 + * On Otoro, SWITCH_NAME could be Headset/No Device when plug/unplug. + * change@/devices/virtual/switch/h2w + * ACTION=change + * DEVPATH=/devices/virtual/switch/h2w + * SUBSYSTEM=switch + * SWITCH_NAME=Headset + * SWITCH_STATE=1 // Headset with mic + * SEQNUM=1602 + */ +class SwitchHandlerHeadphone: public SwitchHandler +{ +public: + SwitchHandlerHeadphone(const char* aDevPath) : + SwitchHandler(aDevPath, SWITCH_HEADPHONES) + { + SwitchHandler::GetInitialState(); + } + + virtual ~SwitchHandlerHeadphone() { } + +protected: + SwitchState ConvertState(const char* aState) + { + MOZ_ASSERT(aState); + + return aState[0] == '0' ? SWITCH_STATE_OFF : + (aState[0] == '1' ? SWITCH_STATE_HEADSET : SWITCH_STATE_HEADPHONE); + } +}; + + +typedef nsTArray<RefPtr<SwitchHandler> > SwitchHandlerArray; + +class SwitchEventRunnable : public Runnable +{ +public: + SwitchEventRunnable(SwitchEvent& aEvent) : mEvent(aEvent) + { + } + + NS_IMETHOD Run() override + { + NotifySwitchChange(mEvent); + return NS_OK; + } +private: + SwitchEvent mEvent; +}; + +class SwitchEventObserver final : public IUeventObserver +{ + ~SwitchEventObserver() + { + mHandler.Clear(); + } + +public: + NS_INLINE_DECL_REFCOUNTING(SwitchEventObserver) + SwitchEventObserver() + : mEnableCount(0), + mHeadphonesFromInputDev(false) + { + Init(); + } + + int GetEnableCount() + { + return mEnableCount; + } + + void EnableSwitch(SwitchDevice aDevice) + { + mEventInfo[aDevice].mEnabled = true; + mEnableCount++; + } + + void DisableSwitch(SwitchDevice aDevice) + { + mEventInfo[aDevice].mEnabled = false; + mEnableCount--; + } + + void Notify(const NetlinkEvent& aEvent) + { + SwitchState currState; + + SwitchDevice device = GetEventInfo(aEvent, currState); + if (device == SWITCH_DEVICE_UNKNOWN) { + return; + } + + EventInfo& info = mEventInfo[device]; + if (currState == info.mEvent.status()) { + return; + } + + info.mEvent.status() = currState; + + if (info.mEnabled) { + NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); + } + } + + void Notify(SwitchDevice aDevice, SwitchState aState) + { + EventInfo& info = mEventInfo[aDevice]; + if (aState == info.mEvent.status()) { + return; + } + + info.mEvent.status() = aState; + + if (info.mEnabled) { + NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); + } + } + + SwitchState GetCurrentInformation(SwitchDevice aDevice) + { + return mEventInfo[aDevice].mEvent.status(); + } + + void NotifyAnEvent(SwitchDevice aDevice) + { + EventInfo& info = mEventInfo[aDevice]; + if (info.mEvent.status() != SWITCH_STATE_UNKNOWN) { + NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); + } + } + + bool GetHeadphonesFromInputDev() + { + return mHeadphonesFromInputDev; + } + +private: + class EventInfo + { + public: + EventInfo() : mEnabled(false) + { + mEvent.status() = SWITCH_STATE_UNKNOWN; + mEvent.device() = SWITCH_DEVICE_UNKNOWN; + } + SwitchEvent mEvent; + bool mEnabled; + }; + + EventInfo mEventInfo[NUM_SWITCH_DEVICE]; + size_t mEnableCount; + SwitchHandlerArray mHandler; + bool mHeadphonesFromInputDev; + + // This function might also get called on the main thread + // (from IsHeadphoneEventFromInputDev) + void Init() + { + RefPtr<SwitchHandlerHeadphone> switchHeadPhone = + new SwitchHandlerHeadphone(SWITCH_HEADSET_DEVPATH); + + // If the initial state is unknown, it means the headphone event is from input dev + mHeadphonesFromInputDev = switchHeadPhone->GetState() == SWITCH_STATE_UNKNOWN ? true : false; + + if (!mHeadphonesFromInputDev) { + mHandler.AppendElement(switchHeadPhone); + } else { + // If headphone status will be notified from input dev then initialize + // status to "off" and wait for event notification. + mEventInfo[SWITCH_HEADPHONES].mEvent.device() = SWITCH_HEADPHONES; + mEventInfo[SWITCH_HEADPHONES].mEvent.status() = SWITCH_STATE_OFF; + } + mHandler.AppendElement(new SwitchHandler(SWITCH_USB_DEVPATH_GB, SWITCH_USB)); + mHandler.AppendElement(new SwitchHandlerUsbIcs(SWITCH_USB_DEVPATH_ICS)); + + SwitchHandlerArray::index_type handlerIndex; + SwitchHandlerArray::size_type numHandlers = mHandler.Length(); + + for (handlerIndex = 0; handlerIndex < numHandlers; handlerIndex++) { + SwitchState state = mHandler[handlerIndex]->GetState(); + if (state == SWITCH_STATE_UNKNOWN) { + continue; + } + + SwitchDevice device = mHandler[handlerIndex]->GetType(); + mEventInfo[device].mEvent.device() = device; + mEventInfo[device].mEvent.status() = state; + } + } + + SwitchDevice GetEventInfo(const NetlinkEvent& aEvent, SwitchState& aState) + { + //working around the android code not being const-correct + NetlinkEvent *e = const_cast<NetlinkEvent*>(&aEvent); + + for (size_t i = 0; i < mHandler.Length(); i++) { + if (mHandler[i]->CheckEvent(e)) { + aState = mHandler[i]->GetState(); + return mHandler[i]->GetType(); + } + } + return SWITCH_DEVICE_UNKNOWN; + } +}; + +static RefPtr<SwitchEventObserver> sSwitchObserver; + +static void +InitializeResourceIfNeed() +{ + if (!sSwitchObserver) { + sSwitchObserver = new SwitchEventObserver(); + RegisterUeventListener(sSwitchObserver); + } +} + +static void +ReleaseResourceIfNeed() +{ + if (sSwitchObserver->GetEnableCount() == 0) { + UnregisterUeventListener(sSwitchObserver); + sSwitchObserver = nullptr; + } +} + +static void +EnableSwitchNotificationsIOThread(SwitchDevice aDevice, Monitor *aMonitor) +{ + InitializeResourceIfNeed(); + sSwitchObserver->EnableSwitch(aDevice); + { + MonitorAutoLock lock(*aMonitor); + lock.Notify(); + } + + // Notify the latest state if IO thread has the information. + if (sSwitchObserver->GetEnableCount() > 1) { + sSwitchObserver->NotifyAnEvent(aDevice); + } +} + +void +EnableSwitchNotifications(SwitchDevice aDevice) +{ + Monitor monitor("EnableSwitch.monitor"); + { + MonitorAutoLock lock(monitor); + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(EnableSwitchNotificationsIOThread, aDevice, &monitor)); + lock.Wait(); + } +} + +static void +DisableSwitchNotificationsIOThread(SwitchDevice aDevice) +{ + MOZ_ASSERT(sSwitchObserver->GetEnableCount()); + sSwitchObserver->DisableSwitch(aDevice); + ReleaseResourceIfNeed(); +} + +void +DisableSwitchNotifications(SwitchDevice aDevice) +{ + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(DisableSwitchNotificationsIOThread, aDevice)); +} + +SwitchState +GetCurrentSwitchState(SwitchDevice aDevice) +{ + MOZ_ASSERT(sSwitchObserver && sSwitchObserver->GetEnableCount()); + return sSwitchObserver->GetCurrentInformation(aDevice); +} + +static void +NotifySwitchStateIOThread(SwitchDevice aDevice, SwitchState aState) +{ + InitializeResourceIfNeed(); + sSwitchObserver->Notify(aDevice, aState); +} + +void NotifySwitchStateFromInputDevice(SwitchDevice aDevice, SwitchState aState) +{ + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(NotifySwitchStateIOThread, aDevice, aState)); +} + +bool IsHeadphoneEventFromInputDev() +{ + // Instead of calling InitializeResourceIfNeed, create new SwitchEventObserver + // to prevent calling RegisterUeventListener in main thread. + RefPtr<SwitchEventObserver> switchObserver = new SwitchEventObserver(); + return switchObserver->GetHeadphonesFromInputDev(); +} + +} // hal_impl +} //mozilla diff --git a/hal/gonk/SensorsTypes.h b/hal/gonk/SensorsTypes.h new file mode 100644 index 000000000..35c852f5a --- /dev/null +++ b/hal/gonk/SensorsTypes.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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 hal_gonk_SensorsTypes_h +#define hal_gonk_SensorsTypes_h + +namespace mozilla { +namespace hal { + +enum SensorsDeliveryMode { + SENSORS_DELIVERY_MODE_BEST_EFFORT, + SENSORS_DELIVERY_MODE_IMMEDIATE +}; + +enum SensorsError { + SENSORS_ERROR_NONE, + SENSORS_ERROR_FAIL, + SENSORS_ERROR_NOT_READY, + SENSORS_ERROR_NOMEM, + SENSORS_ERROR_BUSY, + SENSORS_ERROR_DONE, + SENSORS_ERROR_UNSUPPORTED, + SENSORS_ERROR_PARM_INVALID +}; + +enum SensorsStatus { + SENSORS_STATUS_NO_CONTACT, + SENSORS_STATUS_UNRELIABLE, + SENSORS_STATUS_ACCURACY_LOW, + SENSORS_STATUS_ACCURACY_MEDIUM, + SENSORS_STATUS_ACCURACY_HIGH +}; + +enum SensorsTriggerMode { + SENSORS_TRIGGER_MODE_CONTINUOUS, + SENSORS_TRIGGER_MODE_ON_CHANGE, + SENSORS_TRIGGER_MODE_ONE_SHOT, + SENSORS_TRIGGER_MODE_SPECIAL +}; + +enum SensorsType { + SENSORS_TYPE_ACCELEROMETER, + SENSORS_TYPE_GEOMAGNETIC_FIELD, + SENSORS_TYPE_ORIENTATION, + SENSORS_TYPE_GYROSCOPE, + SENSORS_TYPE_LIGHT, + SENSORS_TYPE_PRESSURE, + SENSORS_TYPE_TEMPERATURE, + SENSORS_TYPE_PROXIMITY, + SENSORS_TYPE_GRAVITY, + SENSORS_TYPE_LINEAR_ACCELERATION, + SENSORS_TYPE_ROTATION_VECTOR, + SENSORS_TYPE_RELATIVE_HUMIDITY, + SENSORS_TYPE_AMBIENT_TEMPERATURE, + SENSORS_TYPE_MAGNETIC_FIELD_UNCALIBRATED, + SENSORS_TYPE_GAME_ROTATION_VECTOR, + SENSORS_TYPE_GYROSCOPE_UNCALIBRATED, + SENSORS_TYPE_SIGNIFICANT_MOTION, + SENSORS_TYPE_STEP_DETECTED, + SENSORS_TYPE_STEP_COUNTER, + SENSORS_TYPE_GEOMAGNETIC_ROTATION_VECTOR, + SENSORS_TYPE_HEART_RATE, + SENSORS_TYPE_TILT_DETECTOR, + SENSORS_TYPE_WAKE_GESTURE, + SENSORS_TYPE_GLANCE_GESTURE, + SENSORS_TYPE_PICK_UP_GESTURE, + SENSORS_TYPE_WRIST_TILT_GESTURE, + SENSORS_NUM_TYPES +}; + +struct SensorsEvent { + SensorsType mType; + SensorsStatus mStatus; + SensorsDeliveryMode mDeliveryMode; + int64_t mTimestamp; + union { + float mFloat[6]; + uint64_t mUint[1]; + } mData; +}; + +/** + * |SensorsSensor| represents a device sensor; either single or composite. + */ +struct SensorsSensor { + SensorsSensor(int32_t aId, SensorsType aType, + float aRange, float aResolution, + float aPower, int32_t aMinPeriod, + int32_t aMaxPeriod, + SensorsTriggerMode aTriggerMode, + SensorsDeliveryMode aDeliveryMode) + : mId(aId) + , mType(aType) + , mRange(aRange) + , mResolution(aResolution) + , mPower(aPower) + , mMinPeriod(aMinPeriod) + , mMaxPeriod(aMaxPeriod) + , mTriggerMode(aTriggerMode) + , mDeliveryMode(aDeliveryMode) + { } + + int32_t mId; + SensorsType mType; + float mRange; + float mResolution; + float mPower; + int32_t mMinPeriod; + int32_t mMaxPeriod; + SensorsTriggerMode mTriggerMode; + SensorsDeliveryMode mDeliveryMode; +}; + +/** + * |SensorClass| represents the status of a specific sensor type. + */ +struct SensorsSensorClass { + SensorsSensorClass() + : mActivated(0) + , mMinValue(0) + , mMaxValue(0) + { } + + void UpdateFromSensor(const SensorsSensor& aSensor) + { + mMaxValue = std::max(aSensor.mRange, mMaxValue); + } + + uint32_t mActivated; + float mMinValue; + float mMaxValue; +}; + +} // namespace hal +} // namespace mozilla + +#endif // hal_gonk_SensorsTypes_h diff --git a/hal/gonk/SystemService.cpp b/hal/gonk/SystemService.cpp new file mode 100644 index 000000000..2b98f5fdd --- /dev/null +++ b/hal/gonk/SystemService.cpp @@ -0,0 +1,131 @@ +/* -*- 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 <cutils/properties.h> +#include <stdio.h> +#include <string.h> + +#include "HalLog.h" +#include "nsITimer.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace hal_impl { + +static const int sRetryInterval = 100; // ms + +bool +SystemServiceIsRunning(const char* aSvcName) +{ + MOZ_ASSERT(NS_IsMainThread()); + + char key[PROPERTY_KEY_MAX]; + auto res = snprintf(key, sizeof(key), "init.svc.%s", aSvcName); + + if (res < 0) { + HAL_ERR("snprintf: %s", strerror(errno)); + return false; + } else if (static_cast<size_t>(res) >= sizeof(key)) { + HAL_ERR("snprintf: trunctated service name %s", aSvcName); + return false; + } + + char value[PROPERTY_VALUE_MAX]; + Unused << NS_WARN_IF(property_get(key, value, "") < 0); + + return !strcmp(value, "running"); +} + +class StartSystemServiceTimerCallback final : public nsITimerCallback +{ + NS_DECL_THREADSAFE_ISUPPORTS; + +public: + StartSystemServiceTimerCallback(const char* aSvcName, const char* aArgs) + : mSvcName(aSvcName) + , mArgs(aArgs) + { + MOZ_COUNT_CTOR_INHERITED(StartSystemServiceTimerCallback, + nsITimerCallback); + } + + NS_IMETHOD Notify(nsITimer* aTimer) override + { + MOZ_ASSERT(NS_IsMainThread()); + + return StartSystemService(mSvcName.get(), mArgs.get()); + } + +protected: + ~StartSystemServiceTimerCallback() + { + MOZ_COUNT_DTOR_INHERITED(StartSystemServiceTimerCallback, + nsITimerCallback); + } + +private: + nsCString mSvcName; + nsCString mArgs; +}; + +NS_IMPL_ISUPPORTS0(StartSystemServiceTimerCallback); + +nsresult +StartSystemService(const char* aSvcName, const char* aArgs) +{ + MOZ_ASSERT(NS_IsMainThread()); + + char value[PROPERTY_VALUE_MAX]; + auto res = snprintf(value, sizeof(value), "%s:%s", aSvcName, aArgs); + + if (res < 0) { + HAL_ERR("snprintf: %s", strerror(errno)); + return NS_ERROR_FAILURE; + } else if (static_cast<size_t>(res) >= sizeof(value)) { + HAL_ERR("snprintf: trunctated service name %s", aSvcName); + return NS_ERROR_OUT_OF_MEMORY; + } + + if (NS_WARN_IF(property_set("ctl.start", value) < 0)) { + return NS_ERROR_FAILURE; + } + + /* If the system service is not running, re-try later to start it. + * + * This condition happens when we restart a service immediately + * after it crashed, as the service state remains 'stopping' + * instead of 'stopped'. Due to the limitation of property service, + * hereby add delay. See Bug 1143925 Comment 41. + */ + if (!SystemServiceIsRunning(aSvcName)) { + nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1"); + if (!timer) { + return NS_ERROR_FAILURE; + } + + RefPtr<StartSystemServiceTimerCallback> timerCallback = + new StartSystemServiceTimerCallback(aSvcName, aArgs); + + timer->InitWithCallback(timerCallback, + sRetryInterval, + nsITimer::TYPE_ONE_SHOT); + } + + return NS_OK; +} + +void +StopSystemService(const char* aSvcName) +{ + MOZ_ASSERT(NS_IsMainThread()); + + Unused << NS_WARN_IF(property_set("ctl.stop", aSvcName)); +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/gonk/UeventPoller.cpp b/hal/gonk/UeventPoller.cpp new file mode 100644 index 000000000..3fbe850ed --- /dev/null +++ b/hal/gonk/UeventPoller.cpp @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright 2012 Mozilla Foundation and Mozilla contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include <arpa/inet.h> +#include <linux/types.h> +#include <linux/netlink.h> +#include <netinet/in.h> +#include <sys/socket.h> + +#include "HalLog.h" +#include "nsDebug.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/FileUtils.h" +#include "mozilla/Monitor.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#include "UeventPoller.h" + +using namespace mozilla; + +namespace mozilla { +namespace hal_impl { + +static void ShutdownUevent(); + +class NetlinkPoller : public MessageLoopForIO::Watcher +{ +public: + NetlinkPoller() : mSocket(-1), + mIOLoop(MessageLoopForIO::current()) + { + } + + virtual ~NetlinkPoller() {} + + bool OpenSocket(); + + virtual void OnFileCanReadWithoutBlocking(int fd); + + // no writing to the netlink socket + virtual void OnFileCanWriteWithoutBlocking(int fd) + { + MOZ_CRASH("Must not write to netlink socket"); + } + + MessageLoopForIO *GetIOLoop () const { return mIOLoop; } + void RegisterObserver(IUeventObserver *aObserver) + { + mUeventObserverList.AddObserver(aObserver); + } + + void UnregisterObserver(IUeventObserver *aObserver) + { + mUeventObserverList.RemoveObserver(aObserver); + if (mUeventObserverList.Length() == 0) { + ShutdownUevent(); // this will destroy self + } + } + +private: + ScopedClose mSocket; + MessageLoopForIO* mIOLoop; + MessageLoopForIO::FileDescriptorWatcher mReadWatcher; + + const static int kBuffsize = 64 * 1024; + uint8_t mBuffer [kBuffsize]; + + typedef ObserverList<NetlinkEvent> UeventObserverList; + UeventObserverList mUeventObserverList; +}; + +bool +NetlinkPoller::OpenSocket() +{ + mSocket.rwget() = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if (mSocket.get() < 0) { + return false; + } + + int sz = kBuffsize; + + if (setsockopt(mSocket.get(), SOL_SOCKET, SO_RCVBUFFORCE, &sz, + sizeof(sz)) < 0) { + return false; + } + + // add FD_CLOEXEC flag + int flags = fcntl(mSocket.get(), F_GETFD); + if (flags == -1) { + return false; + } + flags |= FD_CLOEXEC; + if (fcntl(mSocket.get(), F_SETFD, flags) == -1) { + return false; + } + + // set non-blocking + if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1) { + return false; + } + + struct sockaddr_nl saddr; + bzero(&saddr, sizeof(saddr)); + saddr.nl_family = AF_NETLINK; + saddr.nl_groups = 1; + saddr.nl_pid = gettid(); + + do { + if (bind(mSocket.get(), (struct sockaddr *)&saddr, sizeof(saddr)) == 0) { + break; + } + + if (errno != EADDRINUSE) { + return false; + } + + if (saddr.nl_pid == 0) { + return false; + } + + // Once there was any other place in the same process assigning saddr.nl_pid by + // gettid(), we can detect it and print warning message. + HAL_LOG("The netlink socket address saddr.nl_pid=%u is in use. " + "Let the kernel re-assign.\n", saddr.nl_pid); + saddr.nl_pid = 0; + } while (true); + + if (!mIOLoop->WatchFileDescriptor(mSocket.get(), + true, + MessageLoopForIO::WATCH_READ, + &mReadWatcher, + this)) { + return false; + } + + return true; +} + +static StaticAutoPtr<NetlinkPoller> sPoller; + +class UeventInitTask : public Runnable +{ + NS_IMETHOD Run() override + { + if (!sPoller) { + return NS_OK; + } + if (sPoller->OpenSocket()) { + return NS_OK; + } + sPoller->GetIOLoop()->PostDelayedTask(MakeAndAddRef<UeventInitTask>(), + 1000); + return NS_OK; + } +}; + +void +NetlinkPoller::OnFileCanReadWithoutBlocking(int fd) +{ + MOZ_ASSERT(fd == mSocket.get()); + while (true) { + int ret = read(fd, mBuffer, kBuffsize); + if (ret == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return; + } + if (errno == EINTR) { + continue; + } + } + if (ret <= 0) { + // fatal error on netlink socket which should not happen + _exit(1); + } + NetlinkEvent netlinkEvent; + netlinkEvent.decode(reinterpret_cast<char*>(mBuffer), ret); + mUeventObserverList.Broadcast(netlinkEvent); + } +} + +static bool sShutdown = false; + +class ShutdownNetlinkPoller; +static StaticAutoPtr<ShutdownNetlinkPoller> sShutdownPoller; +static Monitor* sMonitor = nullptr; + +class ShutdownNetlinkPoller { +public: + ~ShutdownNetlinkPoller() + { + // This is called from KillClearOnShutdown() on the main thread. + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_GetIOMessageLoop()); + + { + MonitorAutoLock lock(*sMonitor); + + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(ShutdownUeventIOThread)); + + while (!sShutdown) { + lock.Wait(); + } + } + + sShutdown = true; + delete sMonitor; + } + + static void MaybeInit() + { + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + if (sShutdown || sMonitor) { + // Don't init twice or init after shutdown. + return; + } + + sMonitor = new Monitor("ShutdownNetlinkPoller.monitor"); + { + ShutdownNetlinkPoller* shutdownPoller = new ShutdownNetlinkPoller(); + + nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([=] () -> void + { + sShutdownPoller = shutdownPoller; + ClearOnShutdown(&sShutdownPoller); // Must run on the main thread. + }); + MOZ_ASSERT(runnable); + MOZ_ALWAYS_SUCCEEDS( + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL)); + } + } +private: + ShutdownNetlinkPoller() = default; + static void ShutdownUeventIOThread() + { + MonitorAutoLock l(*sMonitor); + ShutdownUevent(); // Must run on the IO thread. + sShutdown = true; + l.NotifyAll(); + } +}; + +static void +InitializeUevent() +{ + MOZ_ASSERT(!sPoller); + sPoller = new NetlinkPoller(); + sPoller->GetIOLoop()->PostTask(MakeAndAddRef<UeventInitTask>()); + + ShutdownNetlinkPoller::MaybeInit(); +} + +static void +ShutdownUevent() +{ + sPoller = nullptr; +} + +void +RegisterUeventListener(IUeventObserver *aObserver) +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + if (sShutdown) { + return; + } + + if (!sPoller) { + InitializeUevent(); + } + sPoller->RegisterObserver(aObserver); +} + +void +UnregisterUeventListener(IUeventObserver *aObserver) +{ + MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); + + if (sShutdown) { + return; + } + + sPoller->UnregisterObserver(aObserver); +} + +} // hal_impl +} // mozilla + diff --git a/hal/gonk/UeventPoller.h b/hal/gonk/UeventPoller.h new file mode 100644 index 000000000..ba121cec2 --- /dev/null +++ b/hal/gonk/UeventPoller.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* Copyright 2012 Mozilla Foundation and Mozilla contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _mozilla_uevent_poller_h_ +#define _mozilla_uevent_poller_h_ + +#include <sysutils/NetlinkEvent.h> +#include "mozilla/Observer.h" + +class NetlinkEvent; + +namespace mozilla { +namespace hal_impl { + +typedef mozilla::Observer<NetlinkEvent> IUeventObserver; + +/** + * Register for uevent notification. Note that the method should run on the + * <b> IO Thread </b> + * @aObserver the observer to be added. The observer's Notify() is only called + * on the <b> IO Thread </b> + */ +void RegisterUeventListener(IUeventObserver *aObserver); + +/** + * Unregister for uevent notification. Note that the method should run on the + * <b> IO Thread </b> + * @aObserver the observer to be removed + */ +void UnregisterUeventListener(IUeventObserver *aObserver); + +} +} + +#endif + diff --git a/hal/gonk/fanotify.h b/hal/gonk/fanotify.h new file mode 100644 index 000000000..e715d3bf9 --- /dev/null +++ b/hal/gonk/fanotify.h @@ -0,0 +1,118 @@ +#ifndef _LINUX_FANOTIFY_H +#define _LINUX_FANOTIFY_H + +/* This is a Linux header generated by "make headers_install" */ + +#include <linux/types.h> + +/* the following events that user-space can register for */ +#define FAN_ACCESS 0x00000001 /* File was accessed */ +#define FAN_MODIFY 0x00000002 /* File was modified */ +#define FAN_CLOSE_WRITE 0x00000008 /* Writtable file closed */ +#define FAN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */ +#define FAN_OPEN 0x00000020 /* File was opened */ + +#define FAN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ + +#define FAN_OPEN_PERM 0x00010000 /* File open in perm check */ +#define FAN_ACCESS_PERM 0x00020000 /* File accessed in perm check */ + +#define FAN_ONDIR 0x40000000 /* event occurred against dir */ + +#define FAN_EVENT_ON_CHILD 0x08000000 /* interested in child events */ + +/* helper events */ +#define FAN_CLOSE (FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */ + +/* flags used for fanotify_init() */ +#define FAN_CLOEXEC 0x00000001 +#define FAN_NONBLOCK 0x00000002 + +/* These are NOT bitwise flags. Both bits are used togther. */ +#define FAN_CLASS_NOTIF 0x00000000 +#define FAN_CLASS_CONTENT 0x00000004 +#define FAN_CLASS_PRE_CONTENT 0x00000008 +#define FAN_ALL_CLASS_BITS (FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | \ + FAN_CLASS_PRE_CONTENT) + +#define FAN_UNLIMITED_QUEUE 0x00000010 +#define FAN_UNLIMITED_MARKS 0x00000020 + +#define FAN_ALL_INIT_FLAGS (FAN_CLOEXEC | FAN_NONBLOCK | \ + FAN_ALL_CLASS_BITS | FAN_UNLIMITED_QUEUE |\ + FAN_UNLIMITED_MARKS) + +/* flags used for fanotify_modify_mark() */ +#define FAN_MARK_ADD 0x00000001 +#define FAN_MARK_REMOVE 0x00000002 +#define FAN_MARK_DONT_FOLLOW 0x00000004 +#define FAN_MARK_ONLYDIR 0x00000008 +#define FAN_MARK_MOUNT 0x00000010 +#define FAN_MARK_IGNORED_MASK 0x00000020 +#define FAN_MARK_IGNORED_SURV_MODIFY 0x00000040 +#define FAN_MARK_FLUSH 0x00000080 + +#define FAN_ALL_MARK_FLAGS (FAN_MARK_ADD |\ + FAN_MARK_REMOVE |\ + FAN_MARK_DONT_FOLLOW |\ + FAN_MARK_ONLYDIR |\ + FAN_MARK_MOUNT |\ + FAN_MARK_IGNORED_MASK |\ + FAN_MARK_IGNORED_SURV_MODIFY |\ + FAN_MARK_FLUSH) + +/* + * All of the events - we build the list by hand so that we can add flags in + * the future and not break backward compatibility. Apps will get only the + * events that they originally wanted. Be sure to add new events here! + */ +#define FAN_ALL_EVENTS (FAN_ACCESS |\ + FAN_MODIFY |\ + FAN_CLOSE |\ + FAN_OPEN) + +/* + * All events which require a permission response from userspace + */ +#define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\ + FAN_ACCESS_PERM) + +#define FAN_ALL_OUTGOING_EVENTS (FAN_ALL_EVENTS |\ + FAN_ALL_PERM_EVENTS |\ + FAN_Q_OVERFLOW) + +#define FANOTIFY_METADATA_VERSION 3 + +struct fanotify_event_metadata { + __u32 event_len; + __u8 vers; + __u8 reserved; + __u16 metadata_len; + __u64 mask; + __s32 fd; + __s32 pid; +}; + +struct fanotify_response { + __s32 fd; + __u32 response; +}; + +/* Legit userspace responses to a _PERM event */ +#define FAN_ALLOW 0x01 +#define FAN_DENY 0x02 +/* No fd set in event */ +#define FAN_NOFD -1 + +/* Helper functions to deal with fanotify_event_metadata buffers */ +#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata)) + +#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \ + (struct fanotify_event_metadata*)(((char *)(meta)) + \ + (meta)->event_len)) + +#define FAN_EVENT_OK(meta, len) ((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \ + (long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \ + (long)(meta)->event_len <= (long)(len)) + +#endif /* _LINUX_FANOTIFY_H */ diff --git a/hal/gonk/nsIRecoveryService.idl b/hal/gonk/nsIRecoveryService.idl new file mode 100644 index 000000000..ecbb39c0e --- /dev/null +++ b/hal/gonk/nsIRecoveryService.idl @@ -0,0 +1,39 @@ +/* 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 "nsISupports.idl" + +[scriptable, uuid(bc24fb33-a0c1-49ca-aa43-05f167e02fb6)] +interface nsIRecoveryService : nsISupports +{ + /** + * Possible values of fotaStatus.result. These should stay in sync with + * librecovery/librecovery.h + */ + const long FOTA_UPDATE_UNKNOWN = 0; + const long FOTA_UPDATE_FAIL = 1; + const long FOTA_UPDATE_SUCCESS = 2; + + /** + * Uses recovery to wipe the data and cache partitions. If this call is + * successful, the device should reboot before the function call ever returns. + * + * @throws NS_ERROR_FAILURE when rebooting into recovery fails for some reason. + */ + void factoryReset(in string reason); + + /** + * Use recovery to install an OTA update.zip. If this call is + * successful, the device should reboot before the function call ever returns. + * + * @throws NS_ERROR_FAILURE when rebooting into recovery fails for some reason. + */ + void installFotaUpdate(in string updatePath); + + /** + * @return The status of the last FOTA update. One of FOTA_UPDATE_UNKNOWN, + * FOTA_UPDATE_FAIL, FOTA_UPDATE_SUCCESS. + */ + long getFotaUpdateStatus(); +}; diff --git a/hal/gonk/tavarua.h b/hal/gonk/tavarua.h new file mode 100644 index 000000000..4eb3483a8 --- /dev/null +++ b/hal/gonk/tavarua.h @@ -0,0 +1,484 @@ +#ifndef __LINUX_TAVARUA_H +#define __LINUX_TAVARUA_H + +/* This is a Linux header generated by "make headers_install" */ + +#include <stdint.h> +#include <linux/ioctl.h> +#include <linux/videodev2.h> + + +#undef FM_DEBUG + +/* constants */ +#define RDS_BLOCKS_NUM (4) +#define BYTES_PER_BLOCK (3) +#define MAX_PS_LENGTH (96) +#define MAX_RT_LENGTH (64) + +#define XFRDAT0 (0x20) +#define XFRDAT1 (0x21) +#define XFRDAT2 (0x22) + +#define INTDET_PEEK_MSB (0x88) +#define INTDET_PEEK_LSB (0x26) + +#define RMSSI_PEEK_MSB (0x88) +#define RMSSI_PEEK_LSB (0xA8) + +#define MPX_DCC_BYPASS_POKE_MSB (0x88) +#define MPX_DCC_BYPASS_POKE_LSB (0xC0) + +#define MPX_DCC_PEEK_MSB_REG1 (0x88) +#define MPX_DCC_PEEK_LSB_REG1 (0xC2) + +#define MPX_DCC_PEEK_MSB_REG2 (0x88) +#define MPX_DCC_PEEK_LSB_REG2 (0xC3) + +#define MPX_DCC_PEEK_MSB_REG3 (0x88) +#define MPX_DCC_PEEK_LSB_REG3 (0xC4) + +#define ON_CHANNEL_TH_MSB (0x0B) +#define ON_CHANNEL_TH_LSB (0xA8) + +#define OFF_CHANNEL_TH_MSB (0x0B) +#define OFF_CHANNEL_TH_LSB (0xAC) + +#define ENF_200Khz (1) +#define SRCH200KHZ_OFFSET (7) +#define SRCH_MASK (1 << SRCH200KHZ_OFFSET) + +/* Standard buffer size */ +#define STD_BUF_SIZE (128) +/* Search direction */ +#define SRCH_DIR_UP (0) +#define SRCH_DIR_DOWN (1) + +/* control options */ +#define CTRL_ON (1) +#define CTRL_OFF (0) + +#define US_LOW_BAND (87.5) +#define US_HIGH_BAND (108) + +/* constant for Tx */ + +#define MASK_PI (0x0000FFFF) +#define MASK_PI_MSB (0x0000FF00) +#define MASK_PI_LSB (0x000000FF) +#define MASK_PTY (0x0000001F) +#define MASK_TXREPCOUNT (0x0000000F) + +#undef FMDBG +#ifdef FM_DEBUG + #define FMDBG(fmt, args...) printk(KERN_INFO "tavarua_radio: " fmt, ##args) +#else + #define FMDBG(fmt, args...) +#endif + +#undef FMDERR +#define FMDERR(fmt, args...) printk(KERN_INFO "tavarua_radio: " fmt, ##args) + +#undef FMDBG_I2C +#ifdef FM_DEBUG_I2C + #define FMDBG_I2C(fmt, args...) printk(KERN_INFO "fm_i2c: " fmt, ##args) +#else + #define FMDBG_I2C(fmt, args...) +#endif + +/* function declarations */ +/* FM Core audio paths. */ +#define TAVARUA_AUDIO_OUT_ANALOG_OFF (0) +#define TAVARUA_AUDIO_OUT_ANALOG_ON (1) +#define TAVARUA_AUDIO_OUT_DIGITAL_OFF (0) +#define TAVARUA_AUDIO_OUT_DIGITAL_ON (1) + +int tavarua_set_audio_path(int digital_on, int analog_on); + +/* defines and enums*/ + +#define MARIMBA_A0 0x01010013 +#define MARIMBA_2_1 0x02010204 +#define BAHAMA_1_0 0x0302010A +#define BAHAMA_2_0 0x04020205 +#define WAIT_TIMEOUT 2000 +#define RADIO_INIT_TIME 15 +#define TAVARUA_DELAY 10 +/* + * The frequency is set in units of 62.5 Hz when using V4L2_TUNER_CAP_LOW, + * 62.5 kHz otherwise. + * The tuner is able to have a channel spacing of 50, 100 or 200 kHz. + * tuner->capability is therefore set to V4L2_TUNER_CAP_LOW + * The FREQ_MUL is then: 1 MHz / 62.5 Hz = 16000 + */ +#define FREQ_MUL (1000000 / 62.5) + +enum v4l2_cid_private_tavarua_t { + V4L2_CID_PRIVATE_TAVARUA_SRCHMODE = (V4L2_CID_PRIVATE_BASE + 1), + V4L2_CID_PRIVATE_TAVARUA_SCANDWELL, + V4L2_CID_PRIVATE_TAVARUA_SRCHON, + V4L2_CID_PRIVATE_TAVARUA_STATE, + V4L2_CID_PRIVATE_TAVARUA_TRANSMIT_MODE, + V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK, + V4L2_CID_PRIVATE_TAVARUA_REGION, + V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH, + V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY, + V4L2_CID_PRIVATE_TAVARUA_SRCH_PI, + V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT, + V4L2_CID_PRIVATE_TAVARUA_EMPHASIS, + V4L2_CID_PRIVATE_TAVARUA_RDS_STD, + V4L2_CID_PRIVATE_TAVARUA_SPACING, + V4L2_CID_PRIVATE_TAVARUA_RDSON, + V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC, + V4L2_CID_PRIVATE_TAVARUA_LP_MODE, + V4L2_CID_PRIVATE_TAVARUA_ANTENNA, + V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF, + V4L2_CID_PRIVATE_TAVARUA_PSALL, + /*v4l2 Tx controls*/ + V4L2_CID_PRIVATE_TAVARUA_TX_SETPSREPEATCOUNT, + V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_PS_NAME, + V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_RT, + V4L2_CID_PRIVATE_TAVARUA_IOVERC, + V4L2_CID_PRIVATE_TAVARUA_INTDET, + V4L2_CID_PRIVATE_TAVARUA_MPX_DCC, + V4L2_CID_PRIVATE_TAVARUA_AF_JUMP, + V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA, + V4L2_CID_PRIVATE_TAVARUA_HLSI, + + /* + * Here we have IOCTl's that are specific to IRIS + * (V4L2_CID_PRIVATE_BASE + 0x1E to V4L2_CID_PRIVATE_BASE + 0x28) + */ + V4L2_CID_PRIVATE_SOFT_MUTE,/* 0x800001E*/ + V4L2_CID_PRIVATE_RIVA_ACCS_ADDR, + V4L2_CID_PRIVATE_RIVA_ACCS_LEN, + V4L2_CID_PRIVATE_RIVA_PEEK, + V4L2_CID_PRIVATE_RIVA_POKE, + V4L2_CID_PRIVATE_SSBI_ACCS_ADDR, + V4L2_CID_PRIVATE_SSBI_PEEK, + V4L2_CID_PRIVATE_SSBI_POKE, + V4L2_CID_PRIVATE_TX_TONE, + V4L2_CID_PRIVATE_RDS_GRP_COUNTERS, + V4L2_CID_PRIVATE_SET_NOTCH_FILTER,/* 0x8000028 */ + + V4L2_CID_PRIVATE_TAVARUA_SET_AUDIO_PATH,/* 0x8000029 */ + V4L2_CID_PRIVATE_TAVARUA_DO_CALIBRATION,/* 0x800002A : IRIS */ + V4L2_CID_PRIVATE_TAVARUA_SRCH_ALGORITHM,/* 0x800002B */ + V4L2_CID_PRIVATE_IRIS_GET_SINR, /* 0x800002C : IRIS */ + V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD, /* 0x800002D */ + V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD, /* 0x800002E */ + V4L2_CID_PRIVATE_SINR_THRESHOLD, /* 0x800002F : IRIS */ + V4L2_CID_PRIVATE_SINR_SAMPLES, /* 0x8000030 : IRIS */ + +}; + +enum tavarua_buf_t { + TAVARUA_BUF_SRCH_LIST, + TAVARUA_BUF_EVENTS, + TAVARUA_BUF_RT_RDS, + TAVARUA_BUF_PS_RDS, + TAVARUA_BUF_RAW_RDS, + TAVARUA_BUF_AF_LIST, + TAVARUA_BUF_MAX +}; + +enum tavarua_xfr_t { + TAVARUA_XFR_SYNC, + TAVARUA_XFR_ERROR, + TAVARUA_XFR_SRCH_LIST, + TAVARUA_XFR_RT_RDS, + TAVARUA_XFR_PS_RDS, + TAVARUA_XFR_AF_LIST, + TAVARUA_XFR_MAX +}; + +enum channel_spacing { + FM_CH_SPACE_200KHZ, + FM_CH_SPACE_100KHZ, + FM_CH_SPACE_50KHZ +}; + +enum step_size { + NO_SRCH200khz, + ENF_SRCH200khz +}; + +enum emphasis { + EMP_75, + EMP_50 +}; + +enum rds_std { + RBDS_STD, + RDS_STD +}; + +/* offsets */ +#define RAW_RDS 0x0F +#define RDS_BLOCK 3 + +/* registers*/ +#define MARIMBA_XO_BUFF_CNTRL 0x07 +#define RADIO_REGISTERS 0x30 +#define XFR_REG_NUM 16 +#define STATUS_REG_NUM 3 + +/* TX constants */ +#define HEADER_SIZE 4 +#define TX_ON 0x80 +#define TAVARUA_TX_RT RDS_RT_0 +#define TAVARUA_TX_PS RDS_PS_0 + +enum register_t { + STATUS_REG1 = 0, + STATUS_REG2, + STATUS_REG3, + RDCTRL, + FREQ, + TUNECTRL, + SRCHRDS1, + SRCHRDS2, + SRCHCTRL, + IOCTRL, + RDSCTRL, + ADVCTRL, + AUDIOCTRL, + RMSSI, + IOVERC, + AUDIOIND = 0x1E, + XFRCTRL, + FM_CTL0 = 0xFF, + LEAKAGE_CNTRL = 0xFE, +}; +#define BAHAMA_RBIAS_CTL1 0x07 +#define BAHAMA_FM_MODE_REG 0xFD +#define BAHAMA_FM_CTL1_REG 0xFE +#define BAHAMA_FM_CTL0_REG 0xFF +#define BAHAMA_FM_MODE_NORMAL 0x00 +#define BAHAMA_LDO_DREG_CTL0 0xF0 +#define BAHAMA_LDO_AREG_CTL0 0xF4 + +/* Radio Control */ +#define RDCTRL_STATE_OFFSET 0 +#define RDCTRL_STATE_MASK (3 << RDCTRL_STATE_OFFSET) +#define RDCTRL_BAND_OFFSET 2 +#define RDCTRL_BAND_MASK (1 << RDCTRL_BAND_OFFSET) +#define RDCTRL_CHSPACE_OFFSET 3 +#define RDCTRL_CHSPACE_MASK (3 << RDCTRL_CHSPACE_OFFSET) +#define RDCTRL_DEEMPHASIS_OFFSET 5 +#define RDCTRL_DEEMPHASIS_MASK (1 << RDCTRL_DEEMPHASIS_OFFSET) +#define RDCTRL_HLSI_OFFSET 6 +#define RDCTRL_HLSI_MASK (3 << RDCTRL_HLSI_OFFSET) +#define RDSAF_OFFSET 6 +#define RDSAF_MASK (1 << RDSAF_OFFSET) + +/* Tune Control */ +#define TUNE_STATION 0x01 +#define ADD_OFFSET (1 << 1) +#define SIGSTATE (1 << 5) +#define MOSTSTATE (1 << 6) +#define RDSSYNC (1 << 7) +/* Search Control */ +#define SRCH_MODE_OFFSET 0 +#define SRCH_MODE_MASK (7 << SRCH_MODE_OFFSET) +#define SRCH_DIR_OFFSET 3 +#define SRCH_DIR_MASK (1 << SRCH_DIR_OFFSET) +#define SRCH_DWELL_OFFSET 4 +#define SRCH_DWELL_MASK (7 << SRCH_DWELL_OFFSET) +#define SRCH_STATE_OFFSET 7 +#define SRCH_STATE_MASK (1 << SRCH_STATE_OFFSET) + +/* I/O Control */ +#define IOC_HRD_MUTE 0x03 +#define IOC_SFT_MUTE (1 << 2) +#define IOC_MON_STR (1 << 3) +#define IOC_SIG_BLND (1 << 4) +#define IOC_INTF_BLND (1 << 5) +#define IOC_ANTENNA (1 << 6) +#define IOC_ANTENNA_OFFSET 6 +#define IOC_ANTENNA_MASK (1 << IOC_ANTENNA_OFFSET) + +/* RDS Control */ +#define RDS_ON 0x01 +#define RDSCTRL_STANDARD_OFFSET 1 +#define RDSCTRL_STANDARD_MASK (1 << RDSCTRL_STANDARD_OFFSET) + +/* Advanced features controls */ +#define RDSRTEN (1 << 3) +#define RDSPSEN (1 << 4) + +/* Audio path control */ +#define AUDIORX_ANALOG_OFFSET 0 +#define AUDIORX_ANALOG_MASK (1 << AUDIORX_ANALOG_OFFSET) +#define AUDIORX_DIGITAL_OFFSET 1 +#define AUDIORX_DIGITAL_MASK (1 << AUDIORX_DIGITAL_OFFSET) +#define AUDIOTX_OFFSET 2 +#define AUDIOTX_MASK (1 << AUDIOTX_OFFSET) +#define I2SCTRL_OFFSET 3 +#define I2SCTRL_MASK (1 << I2SCTRL_OFFSET) + +/* Search options */ +enum search_t { + SEEK, + SCAN, + SCAN_FOR_STRONG, + SCAN_FOR_WEAK, + RDS_SEEK_PTY, + RDS_SCAN_PTY, + RDS_SEEK_PI, + RDS_AF_JUMP, +}; + +enum audio_path { + FM_DIGITAL_PATH, + FM_ANALOG_PATH +}; +#define SRCH_MODE 0x07 +#define SRCH_DIR 0x08 /* 0-up 1-down */ +#define SCAN_DWELL 0x70 +#define SRCH_ON 0x80 + +/* RDS CONFIG */ +#define RDS_CONFIG_PSALL 0x01 + +#define FM_ENABLE 0x22 +#define SET_REG_FIELD(reg, val, offset, mask) \ + (reg = (reg & ~mask) | (((val) << offset) & mask)) +#define GET_REG_FIELD(reg, offset, mask) ((reg & mask) >> offset) +#define RSH_DATA(val, offset) ((val) >> (offset)) +#define LSH_DATA(val, offset) ((val) << (offset)) +#define GET_ABS_VAL(val) ((val) & (0xFF)) + +enum radio_state_t { + FM_OFF, + FM_RECV, + FM_TRANS, + FM_RESET, +}; + +#define XFRCTRL_WRITE (1 << 7) + +/* Interrupt status */ + +/* interrupt register 1 */ +#define READY (1 << 0) /* Radio ready after powerup or reset */ +#define TUNE (1 << 1) /* Tune completed */ +#define SEARCH (1 << 2) /* Search completed (read FREQ) */ +#define SCANNEXT (1 << 3) /* Scanning for next station */ +#define SIGNAL (1 << 4) /* Signal indicator change (read SIGSTATE) */ +#define INTF (1 << 5) /* Interference cnt has fallen outside range */ +#define SYNC (1 << 6) /* RDS sync state change (read RDSSYNC) */ +#define AUDIO (1 << 7) /* Audio Control indicator (read AUDIOIND) */ + +/* interrupt register 2 */ +#define RDSDAT (1 << 0) /* New unread RDS data group available */ +#define BLOCKB (1 << 1) /* Block-B match condition exists */ +#define PROGID (1 << 2) /* Block-A or Block-C matched stored PI value*/ +#define RDSPS (1 << 3) /* New RDS Program Service Table available */ +#define RDSRT (1 << 4) /* New RDS Radio Text available */ +#define RDSAF (1 << 5) /* New RDS AF List available */ +#define TXRDSDAT (1 << 6) /* Transmitted an RDS group */ +#define TXRDSDONE (1 << 7) /* RDS raw group one-shot transmit completed */ + +/* interrupt register 3 */ +#define TRANSFER (1 << 0) /* Data transfer (XFR) completed */ +#define RDSPROC (1 << 1) /* Dynamic RDS Processing complete */ +#define ERROR (1 << 7) /* Err occurred.Read code to determine cause */ + + +#define FM_TX_PWR_LVL_0 0 /* Lowest power lvl that can be set for Tx */ +#define FM_TX_PWR_LVL_MAX 7 /* Max power lvl for Tx */ +/* Transfer */ +enum tavarua_xfr_ctrl_t { + RDS_PS_0 = 0x01, + RDS_PS_1, + RDS_PS_2, + RDS_PS_3, + RDS_PS_4, + RDS_PS_5, + RDS_PS_6, + RDS_RT_0, + RDS_RT_1, + RDS_RT_2, + RDS_RT_3, + RDS_RT_4, + RDS_AF_0, + RDS_AF_1, + RDS_CONFIG, + RDS_TX_GROUPS, + RDS_COUNT_0, + RDS_COUNT_1, + RDS_COUNT_2, + RADIO_CONFIG, + RX_CONFIG, + RX_TIMERS, + RX_STATIONS_0, + RX_STATIONS_1, + INT_CTRL, + ERROR_CODE, + CHIPID, + CAL_DAT_0 = 0x20, + CAL_DAT_1, + CAL_DAT_2, + CAL_DAT_3, + CAL_CFG_0, + CAL_CFG_1, + DIG_INTF_0, + DIG_INTF_1, + DIG_AGC_0, + DIG_AGC_1, + DIG_AGC_2, + DIG_AUDIO_0, + DIG_AUDIO_1, + DIG_AUDIO_2, + DIG_AUDIO_3, + DIG_AUDIO_4, + DIG_RXRDS, + DIG_DCC, + DIG_SPUR, + DIG_MPXDCC, + DIG_PILOT, + DIG_DEMOD, + DIG_MOST, + DIG_TX_0, + DIG_TX_1, + PHY_TXGAIN = 0x3B, + PHY_CONFIG, + PHY_TXBLOCK, + PHY_TCB, + XFR_PEEK_MODE = 0x40, + XFR_POKE_MODE = 0xC0, + TAVARUA_XFR_CTRL_MAX +}; + +enum tavarua_evt_t { + TAVARUA_EVT_RADIO_READY, + TAVARUA_EVT_TUNE_SUCC, + TAVARUA_EVT_SEEK_COMPLETE, + TAVARUA_EVT_SCAN_NEXT, + TAVARUA_EVT_NEW_RAW_RDS, + TAVARUA_EVT_NEW_RT_RDS, + TAVARUA_EVT_NEW_PS_RDS, + TAVARUA_EVT_ERROR, + TAVARUA_EVT_BELOW_TH, + TAVARUA_EVT_ABOVE_TH, + TAVARUA_EVT_STEREO, + TAVARUA_EVT_MONO, + TAVARUA_EVT_RDS_AVAIL, + TAVARUA_EVT_RDS_NOT_AVAIL, + TAVARUA_EVT_NEW_SRCH_LIST, + TAVARUA_EVT_NEW_AF_LIST, + TAVARUA_EVT_TXRDSDAT, + TAVARUA_EVT_TXRDSDONE, + TAVARUA_EVT_RADIO_DISABLED +}; + +enum tavarua_region_t { + TAVARUA_REGION_US, + TAVARUA_REGION_EU, + TAVARUA_REGION_JAPAN, + TAVARUA_REGION_JAPAN_WIDE, + TAVARUA_REGION_OTHER +}; + +#endif /* __LINUX_TAVARUA_H */ diff --git a/hal/linux/LinuxMemory.cpp b/hal/linux/LinuxMemory.cpp new file mode 100644 index 000000000..14ed63146 --- /dev/null +++ b/hal/linux/LinuxMemory.cpp @@ -0,0 +1,39 @@ +/* -*- 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 <stdio.h> +#include "Hal.h" + +namespace mozilla { +namespace hal_impl { + +uint32_t +GetTotalSystemMemory() +{ + static uint32_t sTotalMemory; + static bool sTotalMemoryObtained = false; + + if (!sTotalMemoryObtained) { + sTotalMemoryObtained = true; + + FILE* fd = fopen("/proc/meminfo", "r"); + if (!fd) { + return 0; + } + + int rv = fscanf(fd, "MemTotal: %i kB", &sTotalMemory); + + if (fclose(fd) || rv != 1) { + return 0; + } + } + + return sTotalMemory * 1024; +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/linux/LinuxPower.cpp b/hal/linux/LinuxPower.cpp new file mode 100644 index 000000000..70ab4a3f6 --- /dev/null +++ b/hal/linux/LinuxPower.cpp @@ -0,0 +1,167 @@ +/* -*- 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 <unistd.h> +#include <sys/reboot.h> +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#include "MainThreadUtils.h" + +#if defined(MOZ_WIDGET_GONK) +#include "cutils/android_reboot.h" +#include "cutils/properties.h" +#endif + +namespace mozilla { +namespace hal_impl { + +#if (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 19) +static void +PowerCtl(const char* aValue, int aCmd) +{ + // this invokes init's powerctl builtin via /init.rc + property_set("sys.powerctl", aValue); + // device should reboot in few moments, but if it doesn't - call + // android_reboot() to make sure that init isn't stuck somewhere + sleep(10); + HAL_LOG("Powerctl call takes too long, forcing %s.", aValue); + android_reboot(aCmd, 0, nullptr); +} +#endif + +void +Reboot() +{ + if (NS_IsMainThread()) { + nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService(); + if (obsServ) { + obsServ->NotifyObservers(nullptr, "system-reboot", nullptr); + } + } + +#if !defined(MOZ_WIDGET_GONK) + sync(); + reboot(RB_AUTOBOOT); +#elif (ANDROID_VERSION < 19) + android_reboot(ANDROID_RB_RESTART, 0, nullptr); +#else + PowerCtl("reboot", ANDROID_RB_RESTART); +#endif +} + +void +PowerOff() +{ + if (NS_IsMainThread()) { + nsCOMPtr<nsIObserverService> obsServ = services::GetObserverService(); + if (obsServ) { + obsServ->NotifyObservers(nullptr, "system-power-off", nullptr); + } + } + +#if !defined(MOZ_WIDGET_GONK) + sync(); + reboot(RB_POWER_OFF); +#elif (ANDROID_VERSION < 19) + android_reboot(ANDROID_RB_POWEROFF, 0, nullptr); +#else + PowerCtl("shutdown", ANDROID_RB_POWEROFF); +#endif +} + +// Structure to specify how watchdog pthread is going to work. +typedef struct watchdogParam +{ + hal::ShutdownMode mode; // Specify how to shutdown the system. + int32_t timeoutSecs; // Specify the delayed seconds to shutdown the system. + + watchdogParam(hal::ShutdownMode aMode, int32_t aTimeoutSecs) + : mode(aMode), timeoutSecs(aTimeoutSecs) {} +} watchdogParam_t; + +// Function to complusively shut down the system with a given mode. +static void +QuitHard(hal::ShutdownMode aMode) +{ + switch (aMode) + { + case hal::eHalShutdownMode_PowerOff: + PowerOff(); + break; + case hal::eHalShutdownMode_Reboot: + Reboot(); + break; + case hal::eHalShutdownMode_Restart: + // Don't let signal handlers affect forced shutdown. + kill(0, SIGKILL); + // If we can't SIGKILL our process group, something is badly + // wrong. Trying to deliver a catch-able signal to ourselves can + // invoke signal handlers and might cause problems. So try + // _exit() and hope we go away. + _exit(1); + break; + default: + MOZ_CRASH(); + } +} + +// Function to complusively shut down the system with a given mode when timeout. +static void* +ForceQuitWatchdog(void* aParamPtr) +{ + watchdogParam_t* paramPtr = reinterpret_cast<watchdogParam_t*>(aParamPtr); + if (paramPtr->timeoutSecs > 0 && paramPtr->timeoutSecs <= 30) { + // If we shut down normally before the timeout, this thread will + // be harmlessly reaped by the OS. + TimeStamp deadline = + (TimeStamp::Now() + TimeDuration::FromSeconds(paramPtr->timeoutSecs)); + while (true) { + TimeDuration remaining = (deadline - TimeStamp::Now()); + int sleepSeconds = int(remaining.ToSeconds()); + if (sleepSeconds <= 0) { + break; + } + sleep(sleepSeconds); + } + } + hal::ShutdownMode mode = paramPtr->mode; + delete paramPtr; + QuitHard(mode); + return nullptr; +} + +void +StartForceQuitWatchdog(hal::ShutdownMode aMode, int32_t aTimeoutSecs) +{ + // Force-quits are intepreted a little more ferociously on Gonk, + // because while Gecko is in the process of shutting down, the user + // can't call 911, for example. And if we hang on shutdown, bad + // things happen. So, make sure that doesn't happen. + if (aTimeoutSecs <= 0) { + return; + } + + // Use a raw pthread here to insulate ourselves from bugs in other + // Gecko code that we're trying to protect! + // + // Note that we let the watchdog in charge of releasing |paramPtr| + // if the pthread is successfully created. + watchdogParam_t* paramPtr = new watchdogParam_t(aMode, aTimeoutSecs); + pthread_t watchdog; + if (pthread_create(&watchdog, nullptr, + ForceQuitWatchdog, + reinterpret_cast<void*>(paramPtr))) { + // Better safe than sorry. + delete paramPtr; + QuitHard(aMode); + } + // The watchdog thread is off and running now. +} + +} // hal_impl +} // mozilla diff --git a/hal/linux/UPowerClient.cpp b/hal/linux/UPowerClient.cpp new file mode 100644 index 000000000..9f6e04379 --- /dev/null +++ b/hal/linux/UPowerClient.cpp @@ -0,0 +1,508 @@ +/* -*- 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 <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> +#include <mozilla/Attributes.h> +#include <mozilla/dom/battery/Constants.h> +#include "nsAutoRef.h" +#include <cmath> + +/* + * Helper that manages the destruction of glib objects as soon as they leave + * the current scope. + * + * We are specializing nsAutoRef class. + */ + +template <> +class nsAutoRefTraits<GHashTable> : public nsPointerRefTraits<GHashTable> +{ +public: + static void Release(GHashTable* ptr) { g_hash_table_unref(ptr); } +}; + +using namespace mozilla::dom::battery; + +namespace mozilla { +namespace 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. + * @return whether everything went ok. + */ + void UpdateTrackedDeviceSync(); + + /** + * Returns a hash table with the properties of aDevice. + * Note: the caller has to unref the hash table. + */ + GHashTable* GetDevicePropertiesSync(DBusGProxy* aProxy); + void GetDevicePropertiesAsync(DBusGProxy* aProxy); + static void GetDevicePropertiesCallback(DBusGProxy* aProxy, + DBusGProxyCall* aCall, + void* aData); + + /** + * Using the device properties (aHashTable), this method updates the member + * variable storing the values we care about. + */ + void UpdateSavedInfo(GHashTable* aHashTable); + + /** + * Callback used by 'DeviceChanged' signal. + */ + static void DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, + UPowerClient* aListener); + + /** + * Callback used by 'PropertiesChanged' signal. + * This method is called when the the battery level changes. + * (Only with upower >= 0.99) + */ + static void PropertiesChanged(DBusGProxy* aProxy, const gchar*, + GHashTable*, char**, + UPowerClient* aListener); + + /** + * Callback called when mDBusConnection gets a signal. + */ + static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection, + DBusMessage* aMessage, + void* aData); + + // The DBus connection object. + DBusGConnection* mDBusConnection; + + // The DBus proxy object to upower. + DBusGProxy* mUPowerProxy; + + // The path of the tracked device. + gchar* mTrackedDevice; + + // The DBusGProxy for the tracked device. + DBusGProxy* 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() + : mDBusConnection(nullptr) + , mUPowerProxy(nullptr) + , mTrackedDevice(nullptr) + , mTrackedDeviceProxy(nullptr) + , mLevel(kDefaultLevel) + , mCharging(kDefaultCharging) + , mRemainingTime(kDefaultRemainingTime) +{ +} + +UPowerClient::~UPowerClient() +{ + NS_ASSERTION(!mDBusConnection && !mUPowerProxy && !mTrackedDevice && !mTrackedDeviceProxy, + "The observers have not been correctly removed! " + "(StopListening should have been called)"); +} + +void +UPowerClient::BeginListening() +{ + GError* error = nullptr; + mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); + + if (!mDBusConnection) { + HAL_LOG("Failed to open connection to bus: %s\n", error->message); + g_error_free(error); + return; + } + + DBusConnection* dbusConnection = + dbus_g_connection_get_connection(mDBusConnection); + + // Make sure we do not exit the entire program if DBus connection get lost. + dbus_connection_set_exit_on_disconnect(dbusConnection, false); + + // Listening to signals the DBus connection is going to get so we will know + // when it is lost and we will be able to disconnect cleanly. + dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this, + nullptr); + + mUPowerProxy = dbus_g_proxy_new_for_name(mDBusConnection, + "org.freedesktop.UPower", + "/org/freedesktop/UPower", + "org.freedesktop.UPower"); + + UpdateTrackedDeviceSync(); + + /* + * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals. + * If we do that, we would have to disconnect from those in StopListening. + * It's not yet implemented because it requires testing hot plugging and + * removal of a battery. + */ + dbus_g_proxy_add_signal(mUPowerProxy, "DeviceChanged", G_TYPE_STRING, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal(mUPowerProxy, "DeviceChanged", + G_CALLBACK (DeviceChanged), this, nullptr); +} + +void +UPowerClient::StopListening() +{ + // If mDBusConnection isn't initialized, that means we are not really listening. + if (!mDBusConnection) { + return; + } + + dbus_connection_remove_filter( + dbus_g_connection_get_connection(mDBusConnection), + ConnectionSignalFilter, this); + + dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged", + G_CALLBACK (DeviceChanged), this); + + g_free(mTrackedDevice); + mTrackedDevice = nullptr; + + if (mTrackedDeviceProxy) { + dbus_g_proxy_disconnect_signal(mTrackedDeviceProxy, "PropertiesChanged", + G_CALLBACK (PropertiesChanged), this); + + g_object_unref(mTrackedDeviceProxy); + mTrackedDeviceProxy = nullptr; + } + + g_object_unref(mUPowerProxy); + mUPowerProxy = nullptr; + + dbus_g_connection_unref(mDBusConnection); + mDBusConnection = nullptr; + + // We should now show the default values, not the latest we got. + mLevel = kDefaultLevel; + mCharging = kDefaultCharging; + mRemainingTime = kDefaultRemainingTime; +} + +void +UPowerClient::UpdateTrackedDeviceSync() +{ + GType typeGPtrArray = dbus_g_type_get_collection("GPtrArray", + DBUS_TYPE_G_OBJECT_PATH); + GPtrArray* devices = nullptr; + GError* error = nullptr; + + // Reset the current tracked device: + g_free(mTrackedDevice); + mTrackedDevice = nullptr; + + // Reset the current tracked device proxy: + if (mTrackedDeviceProxy) { + dbus_g_proxy_disconnect_signal(mTrackedDeviceProxy, "PropertiesChanged", + G_CALLBACK (PropertiesChanged), this); + + g_object_unref(mTrackedDeviceProxy); + mTrackedDeviceProxy = nullptr; + } + + // If that fails, that likely means upower isn't installed. + if (!dbus_g_proxy_call(mUPowerProxy, "EnumerateDevices", &error, G_TYPE_INVALID, + typeGPtrArray, &devices, G_TYPE_INVALID)) { + HAL_LOG("Error: %s\n", error->message); + g_error_free(error); + return; + } + + /* + * We are looking for the first device that is a battery. + * TODO: we could try to combine more than one battery. + */ + for (guint i=0; i<devices->len; ++i) { + gchar* devicePath = static_cast<gchar*>(g_ptr_array_index(devices, i)); + + DBusGProxy* proxy = dbus_g_proxy_new_from_proxy(mUPowerProxy, + "org.freedesktop.DBus.Properties", + devicePath); + + nsAutoRef<GHashTable> hashTable(GetDevicePropertiesSync(proxy)); + + if (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(hashTable, "Type"))) == sDeviceTypeBattery) { + UpdateSavedInfo(hashTable); + mTrackedDevice = devicePath; + mTrackedDeviceProxy = proxy; + break; + } + + g_object_unref(proxy); + g_free(devicePath); + } + + if (mTrackedDeviceProxy) { + dbus_g_proxy_add_signal(mTrackedDeviceProxy, "PropertiesChanged", + G_TYPE_STRING, + dbus_g_type_get_map("GHashTable", G_TYPE_STRING, + G_TYPE_VALUE), + G_TYPE_STRV, G_TYPE_INVALID); + dbus_g_proxy_connect_signal(mTrackedDeviceProxy, "PropertiesChanged", + G_CALLBACK (PropertiesChanged), this, nullptr); + } + + g_ptr_array_free(devices, true); +} + +/* static */ void +UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, + UPowerClient* aListener) +{ + if (!aListener->mTrackedDevice) { + return; + } + +#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16 + if (g_strcmp0(aObjectPath, aListener->mTrackedDevice)) { +#else + if (g_ascii_strcasecmp(aObjectPath, aListener->mTrackedDevice)) { +#endif + return; + } + + aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy); +} + +/* static */ void +UPowerClient::PropertiesChanged(DBusGProxy* aProxy, const gchar*, GHashTable*, + char**, UPowerClient* aListener) +{ + aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy); +} + +/* static */ DBusHandlerResult +UPowerClient::ConnectionSignalFilter(DBusConnection* aConnection, + DBusMessage* aMessage, void* aData) +{ + if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) { + static_cast<UPowerClient*>(aData)->StopListening(); + // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection + // might be shared and some other filters might want to do something. + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +GHashTable* +UPowerClient::GetDevicePropertiesSync(DBusGProxy* aProxy) +{ + GError* error = nullptr; + GHashTable* hashTable = nullptr; + GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, + G_TYPE_VALUE); + if (!dbus_g_proxy_call(aProxy, "GetAll", &error, G_TYPE_STRING, + "org.freedesktop.UPower.Device", G_TYPE_INVALID, + typeGHashTable, &hashTable, G_TYPE_INVALID)) { + HAL_LOG("Error: %s\n", error->message); + g_error_free(error); + return nullptr; + } + + return hashTable; +} + +/* static */ void +UPowerClient::GetDevicePropertiesCallback(DBusGProxy* aProxy, + DBusGProxyCall* aCall, void* aData) +{ + GError* error = nullptr; + GHashTable* hashTable = nullptr; + GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, + G_TYPE_VALUE); + if (!dbus_g_proxy_end_call(aProxy, aCall, &error, typeGHashTable, + &hashTable, G_TYPE_INVALID)) { + HAL_LOG("Error: %s\n", error->message); + g_error_free(error); + } else { + sInstance->UpdateSavedInfo(hashTable); + hal::NotifyBatteryChange(hal::BatteryInformation(sInstance->mLevel, + sInstance->mCharging, + sInstance->mRemainingTime)); + g_hash_table_unref(hashTable); + } +} + +void +UPowerClient::GetDevicePropertiesAsync(DBusGProxy* aProxy) +{ + dbus_g_proxy_begin_call(aProxy, "GetAll", GetDevicePropertiesCallback, nullptr, + nullptr, G_TYPE_STRING, + "org.freedesktop.UPower.Device", G_TYPE_INVALID); +} + +void +UPowerClient::UpdateSavedInfo(GHashTable* aHashTable) +{ + 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. + */ + switch (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "State")))) { + case eState_Unknown: + mCharging = kDefaultCharging; + break; + case eState_FullyCharged: + isFull = true; + MOZ_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 { + mLevel = round(g_value_get_double(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "Percentage"))))*0.01; + } + + if (isFull) { + mRemainingTime = 0; + } else { + mRemainingTime = mCharging ? g_value_get_int64(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "TimeToFull"))) + : g_value_get_int64(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "TimeToEmpty"))); + + if (mRemainingTime == kUPowerUnknownRemainingTime) { + mRemainingTime = kUnknownRemainingTime; + } + } +} + +double +UPowerClient::GetLevel() +{ + return mLevel; +} + +bool +UPowerClient::IsCharging() +{ + return mCharging; +} + +double +UPowerClient::GetRemainingTime() +{ + return mRemainingTime; +} + +} // namespace hal_impl +} // namespace mozilla diff --git a/hal/moz.build b/hal/moz.build new file mode 100644 index 000000000..c17379f22 --- /dev/null +++ b/hal/moz.build @@ -0,0 +1,185 @@ +# -*- 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/. + +XPIDL_SOURCES += [ + 'gonk/nsIRecoveryService.idl', +] + +XPIDL_MODULE = 'hal' + +EXPORTS.mozilla += [ + 'Hal.h', + 'HalImpl.h', + 'HalSandbox.h', + 'HalScreenConfiguration.h', + 'HalSensor.h', + 'HalTypes.h', + 'HalWakeLock.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/AndroidAlarm.cpp', + 'android/AndroidSensor.cpp', + 'fallback/FallbackPower.cpp', + 'linux/LinuxMemory.cpp', + ] + # AndroidHal.cpp cannot be built in unified mode because it relies on HalImpl.h. + SOURCES += [ + 'android/AndroidHal.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + UNIFIED_SOURCES += [ + 'gonk/GonkDiskSpaceWatcher.cpp', + 'gonk/GonkSensor.cpp', + 'gonk/GonkSensorsHelpers.cpp', + 'gonk/GonkSensorsInterface.cpp', + 'gonk/GonkSensorsPollInterface.cpp', + 'gonk/GonkSensorsRegistryInterface.cpp', + 'gonk/GonkSwitch.cpp', + 'gonk/SystemService.cpp', + 'gonk/UeventPoller.cpp', + 'linux/LinuxMemory.cpp', + 'linux/LinuxPower.cpp', + ] + # GonkHal.cpp cannot be built in unified mode because it relies on HalImpl.h. + SOURCES += [ + 'gonk/GonkHal.cpp', + ] +elif CONFIG['OS_TARGET'] == 'Linux': + UNIFIED_SOURCES += [ + 'fallback/FallbackAlarm.cpp', + 'fallback/FallbackScreenConfiguration.cpp', + 'fallback/FallbackSensor.cpp', + 'fallback/FallbackVibration.cpp', + 'linux/LinuxMemory.cpp', + 'linux/LinuxPower.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/FallbackAlarm.cpp', + 'fallback/FallbackMemory.cpp', + 'fallback/FallbackPower.cpp', + 'fallback/FallbackScreenConfiguration.cpp', + 'fallback/FallbackVibration.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', + 'fallback/FallbackAlarm.cpp', + 'fallback/FallbackMemory.cpp', + 'fallback/FallbackPower.cpp', + 'fallback/FallbackScreenConfiguration.cpp', + 'fallback/FallbackVibration.cpp', + ] +elif CONFIG['OS_TARGET'] in ('OpenBSD', 'NetBSD', 'FreeBSD', 'DragonFly'): + UNIFIED_SOURCES += [ + 'fallback/FallbackAlarm.cpp', + 'fallback/FallbackMemory.cpp', + 'fallback/FallbackPower.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/FallbackAlarm.cpp', + 'fallback/FallbackBattery.cpp', + 'fallback/FallbackMemory.cpp', + 'fallback/FallbackPower.cpp', + 'fallback/FallbackScreenConfiguration.cpp', + 'fallback/FallbackSensor.cpp', + 'fallback/FallbackVibration.cpp', + ] + +# Fallbacks for backends implemented on Gonk only. +if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk': + UNIFIED_SOURCES += [ + 'fallback/FallbackDiskSpaceWatcher.cpp', + 'fallback/FallbackFactoryReset.cpp', + 'fallback/FallbackProcessPriority.cpp', + 'fallback/FallbackScreenPower.cpp', + 'fallback/FallbackSwitch.cpp', + 'fallback/FallbackSystemService.cpp', + 'fallback/FallbackThreadPriority.cpp', + 'fallback/FallbackTime.cpp', + 'fallback/FallbackWakeLocks.cpp', + ] + +# Fallbacks for backends implemented on Android only. +if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android': + UNIFIED_SOURCES += [ + 'fallback/FallbackNetwork.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' + +if CONFIG['MOZ_GAMEPAD']: + LOCAL_INCLUDES += [ + '/dom/base', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + # So that we can call nsScreenManagerGonk::GetConfiguration(). + LOCAL_INCLUDES += [ + '/widget', + '/widget/gonk', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + LOCAL_INCLUDES += ['%' + '%s/hardware/libhardware_legacy/include' % CONFIG['ANDROID_SOURCE']] + +CFLAGS += CONFIG['GLIB_CFLAGS'] +CFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS'] +CXXFLAGS += CONFIG['GLIB_CFLAGS'] +CXXFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS'] diff --git a/hal/sandbox/PHal.ipdl b/hal/sandbox/PHal.ipdl new file mode 100644 index 000000000..37b69f93f --- /dev/null +++ b/hal/sandbox/PHal.ipdl @@ -0,0 +1,159 @@ +/* -*- 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/GfxMessageUtils.h"; + +using mozilla::dom::ScreenOrientationInternal from "mozilla/dom/ScreenOrientation.h"; +using mozilla::hal::SensorType from "mozilla/HalSensor.h"; +using mozilla::hal::SensorAccuracyType from "mozilla/HalSensor.h"; +using mozilla::hal::WakeLockControl from "mozilla/HalTypes.h"; +using mozilla::hal::SwitchState from "mozilla/HalTypes.h"; +using mozilla::hal::SwitchDevice 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; + SensorAccuracyType accuracy; +}; + +struct NetworkInformation { + uint32_t type; + bool isWifi; + uint32_t dhcpGateway; +}; + +struct SwitchEvent { + SwitchDevice device; + SwitchState status; +}; + +struct WakeLockInformation { + nsString topic; + uint32_t numLocks; + uint32_t numHidden; + uint64_t[] lockingProcesses; +}; + +struct ScreenConfiguration { + nsIntRect rect; + ScreenOrientationInternal orientation; + uint16_t angle; + uint32_t colorDepth; + uint32_t pixelDepth; +}; + +struct SystemTimezoneChangeInformation { + // These timezone offsets are relative to UTC in minutes and + // have already taken daylight saving time (DST) into account. + int32_t oldTimezoneOffsetMinutes; + int32_t newTimezoneOffsetMinutes; +}; + +} // namespace hal + +namespace hal_sandbox { + +nested(upto inside_cpow) sync protocol PHal { + manager PContent; + +child: + async NotifyBatteryChange(BatteryInformation aBatteryInfo); + async NotifyNetworkChange(NetworkInformation aNetworkInfo); + async NotifyWakeLockChange(WakeLockInformation aWakeLockInfo); + async NotifyScreenConfigurationChange(ScreenConfiguration aScreenOrientation); + async NotifySwitchChange(SwitchEvent aEvent); + async NotifySystemClockChange(int64_t aClockDeltaMS); + async NotifySystemTimezoneChange(SystemTimezoneChangeInformation aSystemTimezoneChangeInfo); + +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); + + sync GetScreenEnabled() returns (bool enabled); + async SetScreenEnabled(bool aEnabled); + + sync GetKeyLightEnabled() returns (bool enabled); + async SetKeyLightEnabled(bool aEnabled); + + sync GetCpuSleepAllowed() returns (bool allowed); + async SetCpuSleepAllowed(bool aAllowed); + + sync GetScreenBrightness() returns (double brightness); + async SetScreenBrightness(double aBrightness); + + async AdjustSystemClock(int64_t aDeltaMilliseconds); + async SetTimezone(nsCString aTimezoneSpec); + sync GetTimezone() + returns (nsCString aTimezoneSpec); + sync GetTimezoneOffset() + returns (int32_t aTimezoneOffset); + async EnableSystemClockChangeNotifications(); + async DisableSystemClockChangeNotifications(); + async EnableSystemTimezoneChangeNotifications(); + async DisableSystemTimezoneChangeNotifications(); + + async ModifyWakeLock(nsString aTopic, + WakeLockControl aLockAdjust, + WakeLockControl aHiddenAdjust, + uint64_t aProcessID); + async EnableWakeLockNotifications(); + async DisableWakeLockNotifications(); + sync GetWakeLockInfo(nsString aTopic) + returns (WakeLockInformation aWakeLockInfo); + + async EnableScreenConfigurationNotifications(); + async DisableScreenConfigurationNotifications(); + nested(inside_cpow) sync GetCurrentScreenConfiguration() + returns (ScreenConfiguration aScreenConfiguration); + sync LockScreenOrientation(ScreenOrientationInternal aOrientation) + returns (bool allowed); + async UnlockScreenOrientation(); + + async EnableSwitchNotifications(SwitchDevice aDevice); + async DisableSwitchNotifications(SwitchDevice aDevice); + sync GetCurrentSwitchState(SwitchDevice aDevice) + returns (SwitchState aState); + + async FactoryReset(nsString aReason); + +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 000000000..d33a53a2f --- /dev/null +++ b/hal/sandbox/SandboxHal.cpp @@ -0,0 +1,907 @@ +/* -*- 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/AppProcessChecker.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/TabParent.h" +#include "mozilla/dom/TabChild.h" +#include "mozilla/dom/battery/Types.h" +#include "mozilla/dom/network/Types.h" +#include "mozilla/dom/ScreenOrientation.h" +#include "mozilla/Observer.h" +#include "mozilla/Unused.h" +#include "nsAutoPtr.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, const WindowIdentifier &id) +{ + HAL_LOG("Vibrate: Sending to parent process."); + + AutoTArray<uint32_t, 8> p(pattern); + + WindowIdentifier newID(id); + newID.AppendProcessID(); + Hal()->SendVibrate(p, newID.AsArray(), TabChild::GetFrom(newID.GetWindow())); +} + +void +CancelVibrate(const WindowIdentifier &id) +{ + HAL_LOG("CancelVibrate: Sending to parent process."); + + WindowIdentifier newID(id); + newID.AppendProcessID(); + Hal()->SendCancelVibrate(newID.AsArray(), TabChild::GetFrom(newID.GetWindow())); +} + +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); +} + +void +EnableScreenConfigurationNotifications() +{ + Hal()->SendEnableScreenConfigurationNotifications(); +} + +void +DisableScreenConfigurationNotifications() +{ + Hal()->SendDisableScreenConfigurationNotifications(); +} + +void +GetCurrentScreenConfiguration(ScreenConfiguration* aScreenConfiguration) +{ + Hal()->SendGetCurrentScreenConfiguration(aScreenConfiguration); +} + +bool +LockScreenOrientation(const dom::ScreenOrientationInternal& aOrientation) +{ + bool allowed; + Hal()->SendLockScreenOrientation(aOrientation, &allowed); + return allowed; +} + +void +UnlockScreenOrientation() +{ + Hal()->SendUnlockScreenOrientation(); +} + +bool +GetScreenEnabled() +{ + bool enabled = false; + Hal()->SendGetScreenEnabled(&enabled); + return enabled; +} + +void +SetScreenEnabled(bool aEnabled) +{ + Hal()->SendSetScreenEnabled(aEnabled); +} + +bool +GetKeyLightEnabled() +{ + bool enabled = false; + Hal()->SendGetKeyLightEnabled(&enabled); + return enabled; +} + +void +SetKeyLightEnabled(bool aEnabled) +{ + Hal()->SendSetKeyLightEnabled(aEnabled); +} + +bool +GetCpuSleepAllowed() +{ + bool allowed = true; + Hal()->SendGetCpuSleepAllowed(&allowed); + return allowed; +} + +void +SetCpuSleepAllowed(bool aAllowed) +{ + Hal()->SendSetCpuSleepAllowed(aAllowed); +} + +double +GetScreenBrightness() +{ + double brightness = 0; + Hal()->SendGetScreenBrightness(&brightness); + return brightness; +} + +void +SetScreenBrightness(double aBrightness) +{ + Hal()->SendSetScreenBrightness(aBrightness); +} + +void +AdjustSystemClock(int64_t aDeltaMilliseconds) +{ + Hal()->SendAdjustSystemClock(aDeltaMilliseconds); +} + +void +SetTimezone(const nsCString& aTimezoneSpec) +{ + Hal()->SendSetTimezone(nsCString(aTimezoneSpec)); +} + +nsCString +GetTimezone() +{ + nsCString timezone; + Hal()->SendGetTimezone(&timezone); + return timezone; +} + +int32_t +GetTimezoneOffset() +{ + int32_t timezoneOffset; + Hal()->SendGetTimezoneOffset(&timezoneOffset); + return timezoneOffset; +} + +void +EnableSystemClockChangeNotifications() +{ + Hal()->SendEnableSystemClockChangeNotifications(); +} + +void +DisableSystemClockChangeNotifications() +{ + Hal()->SendDisableSystemClockChangeNotifications(); +} + +void +EnableSystemTimezoneChangeNotifications() +{ + Hal()->SendEnableSystemTimezoneChangeNotifications(); +} + +void +DisableSystemTimezoneChangeNotifications() +{ + Hal()->SendDisableSystemTimezoneChangeNotifications(); +} + +void +Reboot() +{ + NS_RUNTIMEABORT("Reboot() can't be called from sandboxed contexts."); +} + +void +PowerOff() +{ + NS_RUNTIMEABORT("PowerOff() can't be called from sandboxed contexts."); +} + +void +StartForceQuitWatchdog(ShutdownMode aMode, int32_t aTimeoutSecs) +{ + NS_RUNTIMEABORT("StartForceQuitWatchdog() can't be called from sandboxed contexts."); +} + +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, + uint64_t aProcessID) +{ + MOZ_ASSERT(aProcessID != CONTENT_PROCESS_ID_UNKNOWN); + Hal()->SendModifyWakeLock(nsString(aTopic), aLockAdjust, aHiddenAdjust, aProcessID); +} + +void +GetWakeLockInfo(const nsAString &aTopic, WakeLockInformation *aWakeLockInfo) +{ + Hal()->SendGetWakeLockInfo(nsString(aTopic), aWakeLockInfo); +} + +void +EnableSwitchNotifications(SwitchDevice aDevice) +{ + Hal()->SendEnableSwitchNotifications(aDevice); +} + +void +DisableSwitchNotifications(SwitchDevice aDevice) +{ + Hal()->SendDisableSwitchNotifications(aDevice); +} + +SwitchState +GetCurrentSwitchState(SwitchDevice aDevice) +{ + SwitchState state; + Hal()->SendGetCurrentSwitchState(aDevice, &state); + return state; +} + +void +NotifySwitchStateFromInputDevice(SwitchDevice aDevice, SwitchState aState) +{ + Unused << aDevice; + Unused << aState; + NS_RUNTIMEABORT("Only the main process may notify switch state change."); +} + +bool +EnableAlarm() +{ + NS_RUNTIMEABORT("Alarms can't be programmed from sandboxed contexts. Yet."); + return false; +} + +void +DisableAlarm() +{ + NS_RUNTIMEABORT("Alarms can't be programmed from sandboxed contexts. Yet."); +} + +bool +SetAlarm(int32_t aSeconds, int32_t aNanoseconds) +{ + NS_RUNTIMEABORT("Alarms can't be programmed from sandboxed contexts. Yet."); + return false; +} + +void +SetProcessPriority(int aPid, ProcessPriority aPriority, uint32_t aLRU) +{ + NS_RUNTIMEABORT("Only the main process may set processes' priorities."); +} + +void +SetCurrentThreadPriority(ThreadPriority aThreadPriority) +{ + NS_RUNTIMEABORT("Setting current thread priority cannot be called from sandboxed contexts."); +} + +void +SetThreadPriority(PlatformThreadId aThreadId, + ThreadPriority aThreadPriority) +{ + NS_RUNTIMEABORT("Setting thread priority cannot be called from sandboxed contexts."); +} + +void +FactoryReset(FactoryResetReason& aReason) +{ + if (aReason == FactoryResetReason::Normal) { + Hal()->SendFactoryReset(NS_LITERAL_STRING("normal")); + } else if (aReason == FactoryResetReason::Wipe) { + Hal()->SendFactoryReset(NS_LITERAL_STRING("wipe")); + } else if (aReason == FactoryResetReason::Root) { + Hal()->SendFactoryReset(NS_LITERAL_STRING("root")); + } +} + +void +StartDiskSpaceWatcher() +{ + NS_RUNTIMEABORT("StartDiskSpaceWatcher() can't be called from sandboxed contexts."); +} + +void +StopDiskSpaceWatcher() +{ + NS_RUNTIMEABORT("StopDiskSpaceWatcher() can't be called from sandboxed contexts."); +} + +bool IsHeadphoneEventFromInputDev() +{ + NS_RUNTIMEABORT("IsHeadphoneEventFromInputDev() cannot be called from sandboxed contexts."); + return false; +} + +nsresult StartSystemService(const char* aSvcName, const char* aArgs) +{ + NS_RUNTIMEABORT("System services cannot be controlled from sandboxed contexts."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +void StopSystemService(const char* aSvcName) +{ + NS_RUNTIMEABORT("System services cannot be controlled from sandboxed contexts."); +} + +bool SystemServiceIsRunning(const char* aSvcName) +{ + NS_RUNTIMEABORT("System services cannot be controlled from sandboxed contexts."); + return false; +} + +class HalParent : public PHalParent + , public BatteryObserver + , public NetworkObserver + , public ISensorObserver + , public WakeLockObserver + , public ScreenConfigurationObserver + , public SwitchObserver + , public SystemClockChangeObserver + , public SystemTimezoneChangeObserver +{ +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); + hal::UnregisterScreenConfigurationObserver(this); + for (int32_t sensor = SENSOR_UNKNOWN + 1; + sensor < NUM_SENSOR_TYPE; ++sensor) { + hal::UnregisterSensorObserver(SensorType(sensor), this); + } + hal::UnregisterWakeLockObserver(this); + hal::UnregisterSystemClockChangeObserver(this); + hal::UnregisterSystemTimezoneChangeObserver(this); + for (int32_t switchDevice = SWITCH_DEVICE_UNKNOWN + 1; + switchDevice < NUM_SWITCH_DEVICE; ++switchDevice) { + hal::UnregisterSwitchObserver(SwitchDevice(switchDevice), this); + } + } + + virtual bool + RecvVibrate(InfallibleTArray<unsigned int>&& pattern, + InfallibleTArray<uint64_t>&& id, + PBrowserParent *browserParent) override + { + // We give all content vibration permission. + // TabParent *tabParent = TabParent::GetFrom(browserParent); + /* xxxkhuey wtf + nsCOMPtr<nsIDOMWindow> window = + do_QueryInterface(tabParent->GetBrowserDOMWindow()); + */ + WindowIdentifier newID(id, nullptr); + hal::Vibrate(pattern, newID); + return true; + } + + virtual bool + RecvCancelVibrate(InfallibleTArray<uint64_t> &&id, + PBrowserParent *browserParent) override + { + //TabParent *tabParent = TabParent::GetFrom(browserParent); + /* XXXkhuey wtf + nsCOMPtr<nsIDOMWindow> window = + tabParent->GetBrowserDOMWindow(); + */ + WindowIdentifier newID(id, nullptr); + hal::CancelVibrate(newID); + return true; + } + + virtual bool + RecvEnableBatteryNotifications() override { + // We give all content battery-status permission. + hal::RegisterBatteryObserver(this); + return true; + } + + virtual bool + RecvDisableBatteryNotifications() override { + hal::UnregisterBatteryObserver(this); + return true; + } + + virtual bool + RecvGetCurrentBatteryInformation(BatteryInformation* aBatteryInfo) override { + // We give all content battery-status permission. + hal::GetCurrentBatteryInformation(aBatteryInfo); + return true; + } + + void Notify(const BatteryInformation& aBatteryInfo) override { + Unused << SendNotifyBatteryChange(aBatteryInfo); + } + + virtual bool + RecvEnableNetworkNotifications() override { + // We give all content access to this network-status information. + hal::RegisterNetworkObserver(this); + return true; + } + + virtual bool + RecvDisableNetworkNotifications() override { + hal::UnregisterNetworkObserver(this); + return true; + } + + virtual bool + RecvGetCurrentNetworkInformation(NetworkInformation* aNetworkInfo) override { + hal::GetCurrentNetworkInformation(aNetworkInfo); + return true; + } + + void Notify(const NetworkInformation& aNetworkInfo) override { + Unused << SendNotifyNetworkChange(aNetworkInfo); + } + + virtual bool + RecvEnableScreenConfigurationNotifications() override { + // Screen configuration is used to implement CSS and DOM + // properties, so all content already has access to this. + hal::RegisterScreenConfigurationObserver(this); + return true; + } + + virtual bool + RecvDisableScreenConfigurationNotifications() override { + hal::UnregisterScreenConfigurationObserver(this); + return true; + } + + virtual bool + RecvGetCurrentScreenConfiguration(ScreenConfiguration* aScreenConfiguration) override { + hal::GetCurrentScreenConfiguration(aScreenConfiguration); + return true; + } + + virtual bool + RecvLockScreenOrientation(const dom::ScreenOrientationInternal& aOrientation, bool* aAllowed) 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. + *aAllowed = hal::LockScreenOrientation(aOrientation); + return true; + } + + virtual bool + RecvUnlockScreenOrientation() override + { + hal::UnlockScreenOrientation(); + return true; + } + + void Notify(const ScreenConfiguration& aScreenConfiguration) override { + Unused << SendNotifyScreenConfigurationChange(aScreenConfiguration); + } + + virtual bool + RecvGetScreenEnabled(bool* aEnabled) override + { + if (!AssertAppProcessPermission(this, "power")) { + return false; + } + *aEnabled = hal::GetScreenEnabled(); + return true; + } + + virtual bool + RecvSetScreenEnabled(const bool& aEnabled) override + { + if (!AssertAppProcessPermission(this, "power")) { + return false; + } + hal::SetScreenEnabled(aEnabled); + return true; + } + + virtual bool + RecvGetKeyLightEnabled(bool* aEnabled) override + { + if (!AssertAppProcessPermission(this, "power")) { + return false; + } + *aEnabled = hal::GetKeyLightEnabled(); + return true; + } + + virtual bool + RecvSetKeyLightEnabled(const bool& aEnabled) override + { + if (!AssertAppProcessPermission(this, "power")) { + return false; + } + hal::SetKeyLightEnabled(aEnabled); + return true; + } + + virtual bool + RecvGetCpuSleepAllowed(bool* aAllowed) override + { + if (!AssertAppProcessPermission(this, "power")) { + return false; + } + *aAllowed = hal::GetCpuSleepAllowed(); + return true; + } + + virtual bool + RecvSetCpuSleepAllowed(const bool& aAllowed) override + { + if (!AssertAppProcessPermission(this, "power")) { + return false; + } + hal::SetCpuSleepAllowed(aAllowed); + return true; + } + + virtual bool + RecvGetScreenBrightness(double* aBrightness) override + { + if (!AssertAppProcessPermission(this, "power")) { + return false; + } + *aBrightness = hal::GetScreenBrightness(); + return true; + } + + virtual bool + RecvSetScreenBrightness(const double& aBrightness) override + { + if (!AssertAppProcessPermission(this, "power")) { + return false; + } + hal::SetScreenBrightness(aBrightness); + return true; + } + + virtual bool + RecvAdjustSystemClock(const int64_t &aDeltaMilliseconds) override + { + if (!AssertAppProcessPermission(this, "time")) { + return false; + } + hal::AdjustSystemClock(aDeltaMilliseconds); + return true; + } + + virtual bool + RecvSetTimezone(const nsCString& aTimezoneSpec) override + { + if (!AssertAppProcessPermission(this, "time")) { + return false; + } + hal::SetTimezone(aTimezoneSpec); + return true; + } + + virtual bool + RecvGetTimezone(nsCString *aTimezoneSpec) override + { + if (!AssertAppProcessPermission(this, "time")) { + return false; + } + *aTimezoneSpec = hal::GetTimezone(); + return true; + } + + virtual bool + RecvGetTimezoneOffset(int32_t *aTimezoneOffset) override + { + if (!AssertAppProcessPermission(this, "time")) { + return false; + } + *aTimezoneOffset = hal::GetTimezoneOffset(); + return true; + } + + virtual bool + RecvEnableSystemClockChangeNotifications() override + { + hal::RegisterSystemClockChangeObserver(this); + return true; + } + + virtual bool + RecvDisableSystemClockChangeNotifications() override + { + hal::UnregisterSystemClockChangeObserver(this); + return true; + } + + virtual bool + RecvEnableSystemTimezoneChangeNotifications() override + { + hal::RegisterSystemTimezoneChangeObserver(this); + return true; + } + + virtual bool + RecvDisableSystemTimezoneChangeNotifications() override + { + hal::UnregisterSystemTimezoneChangeObserver(this); + return true; + } + + virtual bool + RecvEnableSensorNotifications(const SensorType &aSensor) override { + // We currently allow any content to register device-sensor + // listeners. + hal::RegisterSensorObserver(aSensor, this); + return true; + } + + virtual bool + RecvDisableSensorNotifications(const SensorType &aSensor) override { + hal::UnregisterSensorObserver(aSensor, this); + return true; + } + + void Notify(const SensorData& aSensorData) override { + Unused << SendNotifySensorChange(aSensorData); + } + + virtual bool + RecvModifyWakeLock(const nsString& aTopic, + const WakeLockControl& aLockAdjust, + const WakeLockControl& aHiddenAdjust, + const uint64_t& aProcessID) override + { + MOZ_ASSERT(aProcessID != CONTENT_PROCESS_ID_UNKNOWN); + + // We allow arbitrary content to use wake locks. + hal::ModifyWakeLock(aTopic, aLockAdjust, aHiddenAdjust, aProcessID); + return true; + } + + virtual bool + RecvEnableWakeLockNotifications() override + { + // We allow arbitrary content to use wake locks. + hal::RegisterWakeLockObserver(this); + return true; + } + + virtual bool + RecvDisableWakeLockNotifications() override + { + hal::UnregisterWakeLockObserver(this); + return true; + } + + virtual bool + RecvGetWakeLockInfo(const nsString &aTopic, WakeLockInformation *aWakeLockInfo) override + { + hal::GetWakeLockInfo(aTopic, aWakeLockInfo); + return true; + } + + void Notify(const WakeLockInformation& aWakeLockInfo) override + { + Unused << SendNotifyWakeLockChange(aWakeLockInfo); + } + + virtual bool + RecvEnableSwitchNotifications(const SwitchDevice& aDevice) override + { + // Content has no reason to listen to switch events currently. + hal::RegisterSwitchObserver(aDevice, this); + return true; + } + + virtual bool + RecvDisableSwitchNotifications(const SwitchDevice& aDevice) override + { + hal::UnregisterSwitchObserver(aDevice, this); + return true; + } + + void Notify(const SwitchEvent& aSwitchEvent) override + { + Unused << SendNotifySwitchChange(aSwitchEvent); + } + + virtual bool + RecvGetCurrentSwitchState(const SwitchDevice& aDevice, hal::SwitchState *aState) override + { + // Content has no reason to listen to switch events currently. + *aState = hal::GetCurrentSwitchState(aDevice); + return true; + } + + void Notify(const int64_t& aClockDeltaMS) override + { + Unused << SendNotifySystemClockChange(aClockDeltaMS); + } + + void Notify(const SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo) override + { + Unused << SendNotifySystemTimezoneChange(aSystemTimezoneChangeInfo); + } + + virtual bool + RecvFactoryReset(const nsString& aReason) override + { + if (!AssertAppProcessPermission(this, "power")) { + return false; + } + + FactoryResetReason reason = FactoryResetReason::Normal; + if (aReason.EqualsLiteral("normal")) { + reason = FactoryResetReason::Normal; + } else if (aReason.EqualsLiteral("wipe")) { + reason = FactoryResetReason::Wipe; + } else if (aReason.EqualsLiteral("root")) { + reason = FactoryResetReason::Root; + } else { + // Invalid factory reset reason. That should never happen. + return false; + } + + hal::FactoryReset(reason); + return true; + } +}; + +class HalChild : public PHalChild { +public: + virtual void + ActorDestroy(ActorDestroyReason aWhy) override + { + sHalChildDestroyed = true; + } + + virtual bool + RecvNotifyBatteryChange(const BatteryInformation& aBatteryInfo) override { + hal::NotifyBatteryChange(aBatteryInfo); + return true; + } + + virtual bool + RecvNotifySensorChange(const hal::SensorData &aSensorData) override; + + virtual bool + RecvNotifyNetworkChange(const NetworkInformation& aNetworkInfo) override { + hal::NotifyNetworkChange(aNetworkInfo); + return true; + } + + virtual bool + RecvNotifyWakeLockChange(const WakeLockInformation& aWakeLockInfo) override { + hal::NotifyWakeLockChange(aWakeLockInfo); + return true; + } + + virtual bool + RecvNotifyScreenConfigurationChange(const ScreenConfiguration& aScreenConfiguration) override { + hal::NotifyScreenConfigurationChange(aScreenConfiguration); + return true; + } + + virtual bool + RecvNotifySwitchChange(const mozilla::hal::SwitchEvent& aEvent) override { + hal::NotifySwitchChange(aEvent); + return true; + } + + virtual bool + RecvNotifySystemClockChange(const int64_t& aClockDeltaMS) override { + hal::NotifySystemClockChange(aClockDeltaMS); + return true; + } + + virtual bool + RecvNotifySystemTimezoneChange( + const SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo) override { + hal::NotifySystemTimezoneChange(aSystemTimezoneChangeInfo); + return true; + } +}; + +bool +HalChild::RecvNotifySensorChange(const hal::SensorData &aSensorData) { + hal::NotifySensorChange(aSensorData); + + return true; +} + +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 000000000..0da2d41fb --- /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 000000000..c1b9a31e6 --- /dev/null +++ b/hal/windows/WindowsBattery.cpp @@ -0,0 +1,190 @@ +/* -*- 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 "nsITimer.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/battery/Constants.h" +#include "nsComponentManagerUtils.h" + +#include <windows.h> +#include "mozilla/WindowsVersion.h" + +using namespace mozilla::dom::battery; + +namespace mozilla { +namespace hal_impl { + +static nsCOMPtr<nsITimer> sUpdateTimer; + +/* Power Event API is Vista or later */ +static decltype(RegisterPowerSettingNotification)* sRegisterPowerSettingNotification = nullptr; +static decltype(UnregisterPowerSettingNotification)* sUnregisterPowerSettingNotification = nullptr; +static HPOWERNOTIFY sPowerHandle = nullptr; +static HPOWERNOTIFY sCapacityHandle = nullptr; +static HWND sHWnd = nullptr; + +static void +UpdateHandler(nsITimer* aTimer, void* aClosure) { + NS_ASSERTION(!IsVistaOrLater(), + "We shouldn't call this function for Vista or later version!"); + + static hal::BatteryInformation sLastInfo; + hal::BatteryInformation currentInfo; + + hal_impl::GetCurrentBatteryInformation(¤tInfo); + if (sLastInfo.level() != currentInfo.level() || + sLastInfo.charging() != currentInfo.charging() || + sLastInfo.remainingTime() != currentInfo.remainingTime()) { + hal::NotifyBatteryChange(currentInfo); + sLastInfo = currentInfo; + } +} + +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() +{ + if (IsVistaOrLater()) { + // RegisterPowerSettingNotification is from Vista or later. + // Use this API if available. + HMODULE hUser32 = GetModuleHandleW(L"USER32.DLL"); + if (!sRegisterPowerSettingNotification) + sRegisterPowerSettingNotification = (decltype(RegisterPowerSettingNotification)*) + GetProcAddress(hUser32, "RegisterPowerSettingNotification"); + if (!sUnregisterPowerSettingNotification) + sUnregisterPowerSettingNotification = (decltype(UnregisterPowerSettingNotification)*) + GetProcAddress(hUser32, "UnregisterPowerSettingNotification"); + + if (!sRegisterPowerSettingNotification || + !sUnregisterPowerSettingNotification) { + NS_ASSERTION(false, "Canot find PowerSettingNotification functions."); + return; + } + + // 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 = + sRegisterPowerSettingNotification(sHWnd, + &GUID_ACDC_POWER_SOURCE, + DEVICE_NOTIFY_WINDOW_HANDLE); + sCapacityHandle = + sRegisterPowerSettingNotification(sHWnd, + &GUID_BATTERY_PERCENTAGE_REMAINING, + DEVICE_NOTIFY_WINDOW_HANDLE); + } else + { + // for Windows XP. If we remove Windows XP support, + // we should remove timer-based power notification + sUpdateTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (sUpdateTimer) { + sUpdateTimer->InitWithFuncCallback(UpdateHandler, + nullptr, + Preferences::GetInt("dom.battery.timer", + 30000 /* 30s */), + nsITimer::TYPE_REPEATING_SLACK); + } + } +} + +void +DisableBatteryNotifications() +{ + if (IsVistaOrLater()) { + if (sPowerHandle) { + sUnregisterPowerSettingNotification(sPowerHandle); + sPowerHandle = nullptr; + } + + if (sCapacityHandle) { + sUnregisterPowerSettingNotification(sCapacityHandle); + sCapacityHandle = nullptr; + } + + if (sHWnd) { + DestroyWindow(sHWnd); + sHWnd = nullptr; + } + } else + { + if (sUpdateTimer) { + sUpdateTimer->Cancel(); + sUpdateTimer = 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; + } +} + +} // hal_impl +} // mozilla diff --git a/hal/windows/WindowsSensor.cpp b/hal/windows/WindowsSensor.cpp new file mode 100644 index 000000000..302ad7c48 --- /dev/null +++ b/hal/windows/WindowsSensor.cpp @@ -0,0 +1,184 @@ +/* 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; + InfallibleTArray<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::SENSOR_ACCURACY_UNKNOWN); + 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; + } +} + +} // hal_impl +} // mozilla |