Bug 749551 - Alarm API (Hal/Gonk). r=cjones
authorGene Lian <clian@mozilla.com>
Fri, 06 Jul 2012 12:42:10 +0200
changeset 98491 7b992fc42d0a9312f93a41a45cf258bb76dafd70
parent 98490 b3d4127dd8cefb1405cbc295aebda05c343ac850
child 98492 7ddee2b0e86898b4311c486266e7973d5116a144
push id1316
push userakeybl@mozilla.com
push dateMon, 27 Aug 2012 22:37:00 +0000
treeherdermozilla-esr52@6fdf9985acfe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjones
bugs749551
milestone16.0a1
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
 {