Bug 749551 - Alarm API (Hal/Gonk). r=cjones
authorGene Lian <clian@mozilla.com>
Tue, 03 Jul 2012 11:24:13 +0200
changeset 102978 1e90805d96aab9f2c29431908b722cfb51107847
parent 102977 07425f0e921ec0f55739e5039a1b0abb1844f957
child 102979 23fd9a8d0c9afbd3dc247301304156c3c4949346
push id1316
push userakeybl@mozilla.com
push dateMon, 27 Aug 2012 22:37:00 +0000
treeherdermozilla-beta@db4b09302ee2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones
bugs749551
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 749551 - Alarm API (Hal/Gonk). r=cjones
hal/Hal.cpp
hal/Hal.h
hal/HalInternal.h
hal/Makefile.in
hal/fallback/FallbackAlarm.cpp
hal/gonk/GonkHal.cpp
hal/sandbox/SandboxHal.cpp
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -684,10 +684,47 @@ NotifySwitchChange(const hal::SwitchEven
   // 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());
+}
+
+void
+UnregisterTheOneAlarmObserver()
+{
+  if (sAlarmObserver) {
+    sAlarmObserver = NULL;
+    PROXY_IF_SANDBOXED(DisableAlarm());
+  }
+}
+
+void
+NotifyAlarmFired()
+{
+  if (sAlarmObserver) {
+    sAlarmObserver->Notify(void_t());
+  }
+}
+
+bool
+SetAlarm(long aSeconds, long aNanoseconds)
+{
+  // It's pointless to program an alarm nothing is going to observe ...
+  MOZ_ASSERT(sAlarmObserver);
+  RETURN_PROXY_IF_SANDBOXED(SetAlarm(aSeconds, aNanoseconds));
+}
+
 } // namespace hal
 } // namespace mozilla
--- a/hal/Hal.h
+++ b/hal/Hal.h
@@ -37,16 +37,17 @@ class nsIDOMWindow;
 
 namespace mozilla {
 
 template <class T>
 class Observer;
 
 namespace hal {
 
+typedef Observer<void_t> AlarmObserver;
 typedef Observer<ScreenConfiguration> ScreenConfigurationObserver;
 
 class WindowIdentifier;
 
 extern PRLogModuleInfo *sHalLog;
 #define HAL_LOG(msg) PR_LOG(mozilla::hal::sHalLog, PR_LOG_DEBUG, msg)
 
 } // namespace hal
@@ -361,16 +362,51 @@ void UnregisterSwitchObserver(hal::Switc
  */
 void NotifySwitchChange(const hal::SwitchEvent& aEvent);
 
 /**
  * Get current switch information.
  */
 hal::SwitchState GetCurrentSwitchState(hal::SwitchDevice aDevice);
 
+/**
+ * Register an observer that is notified when a programmed alarm
+ * expires.
+ *
+ * Currently, there can only be 0 or 1 alarm observers.
+ */
+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.
+ */
+bool SetAlarm(long aSeconds, long aNanoseconds);
+
 } // namespace MOZ_HAL_NAMESPACE
 } // namespace mozilla
 
 #ifdef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_HAL_NAMESPACE
 #endif
 
--- a/hal/HalInternal.h
+++ b/hal/HalInternal.h
@@ -59,12 +59,22 @@ void DisableScreenConfigurationNotificat
  */
 void EnableSwitchNotifications(hal::SwitchDevice aDevice);
 
 /**
  * Disable switch notifications from the backend
  */
 void DisableSwitchNotifications(hal::SwitchDevice aDevice);
 
+/**
+ * Enable alarm notifications from the backend.
+ */
+bool EnableAlarm();
+
+/**
+ * Disable alarm notifications from the backend.
+ */
+void DisableAlarm();
+
 } // namespace MOZ_HAL_NAMESPACE
 } // namespace mozilla
 
 #endif  // mozilla_HalInternal_h
--- a/hal/Makefile.in
+++ b/hal/Makefile.in
@@ -42,75 +42,81 @@ CPPSRCS = \
   HalWakeLock.cpp \
   $(NULL)
 
 ifeq (android,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += \
   AndroidHal.cpp \
   AndroidSensor.cpp \
   FallbackPower.cpp \
+  FallbackAlarm.cpp \
   $(NULL)
 else ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += \
   GonkHal.cpp \
   LinuxPower.cpp \
   GonkSensor.cpp \
   UeventPoller.cpp \
   GonkSwitch.cpp \
   $(NULL)
 else ifeq (Linux,$(OS_TARGET))
 CPPSRCS += \
   LinuxPower.cpp \
   FallbackScreenConfiguration.cpp \
   FallbackSensor.cpp \
   FallbackVibration.cpp \
+  FallbackAlarm.cpp \
   $(NULL)
 ifdef MOZ_ENABLE_DBUS
 CPPSRCS += UPowerClient.cpp
 else
 CPPSRCS += FallbackBattery.cpp
 endif
 else ifeq (WINNT,$(OS_TARGET))
 CPPSRCS += \
   WindowsBattery.cpp \
   WindowsSensor.cpp \
   FallbackVibration.cpp \
   FallbackScreenConfiguration.cpp \
   FallbackPower.cpp \
+  FallbackAlarm.cpp \
   $(NULL)
 else ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += \
   FallbackBattery.cpp \
   FallbackVibration.cpp \
   FallbackPower.cpp \
   FallbackScreenConfiguration.cpp \
+  FallbackAlarm.cpp \
   $(NULL)
 CMMSRCS += \
   CocoaSensor.mm \
   smslib.mm \
   $(NULL)
 else ifneq (,$(filter OpenBSD NetBSD FreeBSD DragonFlyBSD,$(OS_TARGET)))
 CPPSRCS += \
   FallbackSensor.cpp \
   FallbackVibration.cpp \
   FallbackPower.cpp \
   FallbackScreenConfiguration.cpp \
+  FallbackAlarm.cpp \
   $(NULL)
 ifdef MOZ_ENABLE_DBUS
 CPPSRCS += UPowerClient.cpp
 else
 CPPSRCS += FallbackBattery.cpp
 endif
 else
 CPPSRCS += \
   FallbackBattery.cpp \
   FallbackSensor.cpp \
   FallbackVibration.cpp \
   FallbackPower.cpp \
   FallbackScreenConfiguration.cpp \
+  FallbackAlarm.cpp \
   $(NULL)
 endif
 
 # Fallbacks for backends implemented on Gonk only.
 ifneq (gonk,$(MOZ_WIDGET_TOOLKIT)) #{
 CPPSRCS += \
   FallbackLights.cpp  \
   FallbackTime.cpp \
@@ -131,9 +137,9 @@ include $(topsrcdir)/config/rules.mk
 
 CFLAGS          += $(MOZ_DBUS_GLIB_CFLAGS)
 CXXFLAGS        += $(MOZ_DBUS_GLIB_CFLAGS)
 
 ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
 # So that we can call nsScreenManagerGonk::GetConfiguration().
 LOCAL_INCLUDES += -I$(topsrcdir)/widget/gonk
 LOCAL_INCLUDES += -I$(topsrcdir)/widget/xpwidgets
-endif
\ No newline at end of file
+endif
new file mode 100644
--- /dev/null
+++ b/hal/fallback/FallbackAlarm.cpp
@@ -0,0 +1,28 @@
+/* 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
+EnableAlarm()
+{
+  return false;
+}
+
+void
+DisableAlarm()
+{
+}
+
+bool
+SetAlarm(long aSeconds, long aNanoseconds)
+{
+  return false;
+}
+
+} // hal_impl
+} // namespace mozilla
--- a/hal/gonk/GonkHal.cpp
+++ b/hal/gonk/GonkHal.cpp
@@ -1,16 +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/. */
 
 #include <errno.h>
 #include <fcntl.h>
+#include <linux/android_alarm.h>
 #include <math.h>
 #include <stdio.h>
 #include <sys/syscall.h>
 #include <time.h>
 
 #include "android/log.h"
 #include "cutils/properties.h"
 #include "hardware/hardware.h"
@@ -621,10 +622,172 @@ LockScreenOrientation(const dom::ScreenO
 }
 
 void
 UnlockScreenOrientation()
 {
   OrientationObserver::GetInstance()->UnlockScreenOrientation();
 }
 
+
+static pthread_t sAlarmFireWatcherThread;
+
+// If |sAlarmData| is non-null, it's owned by the watcher thread.
+typedef struct AlarmData {
+
+public:
+  AlarmData(int aFd) : mFd(aFd), mGeneration(sNextGeneration++), mShuttingDown(false) {}
+  ScopedClose mFd;
+  int mGeneration;
+  bool mShuttingDown;
+
+  static int sNextGeneration;
+
+} AlarmData;
+
+int AlarmData::sNextGeneration = 0;
+
+AlarmData* sAlarmData = NULL;
+
+class AlarmFiredEvent : public nsRunnable {
+
+public:
+  AlarmFiredEvent(int aGeneration) : mGeneration(aGeneration) {}
+
+  NS_IMETHOD Run() {
+    // Guard against spurious notifications caused by an alarm firing
+    // concurrently with it being disabled.
+    if (sAlarmData && !sAlarmData->mShuttingDown && mGeneration == sAlarmData->mGeneration) {
+      hal::NotifyAlarmFired();
+    }
+
+    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 == SIGUSR2) {
+    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)) {
+      NS_DispatchToMainThread(new AlarmFiredEvent(alarmData->mGeneration));
+    }
+  }
+
+  pthread_cleanup_pop(1);
+  return NULL;
+}
+
+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;
+  }
+
+  nsAutoPtr<AlarmData> alarmData(new AlarmData(alarmFd));
+
+  struct sigaction actions;
+  memset(&actions, 0, sizeof(actions));
+  sigemptyset(&actions.sa_mask);
+  actions.sa_flags = 0;
+  actions.sa_handler = ShutDownAlarm;
+  if (sigaction(SIGUSR2, &actions, NULL)) {
+    HAL_LOG(("Failed to set SIGUSR2 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 = NULL;
+    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.forget();
+  return true;
+}
+
+void
+DisableAlarm()
+{
+  MOZ_ASSERT(sAlarmData);
+
+  // NB: this must happen-before the thread cancellation.
+  sAlarmData = NULL;
+
+  // The cancel will interrupt the thread and destroy it, freeing the
+  // data pointed at by sAlarmData.
+  DebugOnly<int> err = pthread_kill(sAlarmFireWatcherThread, SIGUSR2);
+  MOZ_ASSERT(!err);
+}
+
+bool
+SetAlarm(long aSeconds, long 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;
+}
+
 } // hal_impl
 } // mozilla
--- a/hal/sandbox/SandboxHal.cpp
+++ b/hal/sandbox/SandboxHal.cpp
@@ -255,16 +255,36 @@ DisableSwitchNotifications(SwitchDevice 
 SwitchState
 GetCurrentSwitchState(SwitchDevice aDevice)
 {
   SwitchState state;
   Hal()->SendGetCurrentSwitchState(aDevice, &state);
   return state;
 }
 
+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(long aSeconds, long aNanoseconds)
+{
+  NS_RUNTIMEABORT("Alarms can't be programmed from sandboxed contexts.  Yet.");
+  return false;
+}
+
 class HalParent : public PHalParent
                 , public BatteryObserver
                 , public NetworkObserver
                 , public ISensorObserver
                 , public WakeLockObserver
                 , public ScreenConfigurationObserver
                 , public SwitchObserver
 {