Bug 696045 - Implement Mac backend for Battery API. r=BenWa,mounir
authorReuben Morais <reuben.morais@gmail.com>
Fri, 21 Sep 2012 23:36:14 -0400
changeset 114657 294643a303c1b9fa4351a793406daf1ec7b8f0cf
parent 114656 7c8b8d36679ff71e4b01e49a65674dfdb3b58253
child 114658 b22c10ecf39093cbb540e3722245b4bbd638d885
push id239
push userakeybl@mozilla.com
push dateThu, 03 Jan 2013 21:54:43 +0000
treeherdermozilla-release@3a7b66445659 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersBenWa, mounir
bugs696045
milestone18.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 696045 - Implement Mac backend for Battery API. r=BenWa,mounir
hal/Makefile.in
hal/cocoa/CocoaBattery.cpp
--- a/hal/Makefile.in
+++ b/hal/Makefile.in
@@ -78,17 +78,17 @@ CPPSRCS += \
   WindowsSensor.cpp \
   FallbackVibration.cpp \
   FallbackScreenConfiguration.cpp \
   FallbackPower.cpp \
   FallbackAlarm.cpp \
   $(NULL)
 else ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += \
-  FallbackBattery.cpp \
+  CocoaBattery.cpp \
   FallbackVibration.cpp \
   FallbackPower.cpp \
   FallbackScreenConfiguration.cpp \
   FallbackAlarm.cpp \
   $(NULL)
 CMMSRCS += \
   CocoaSensor.mm \
   smslib.mm \
new file mode 100644
--- /dev/null
+++ b/hal/cocoa/CocoaBattery.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: 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 <nsObserverService.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();
+
+  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 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()
+{
+  MacPowerInformationService::GetInstance()->BeginListening();
+}
+
+void
+DisableBatteryNotifications()
+{
+  MacPowerInformationService::GetInstance()->StopListening();
+}
+
+void
+GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo)
+{
+  MacPowerInformationService* powerService = MacPowerInformationService::GetInstance();
+
+  aBatteryInfo->level() = powerService->mLevel;
+  aBatteryInfo->charging() = powerService->mCharging;
+  aBatteryInfo->remainingTime() = powerService->mRemainingTime;
+}
+
+/*
+ * Following is the implementation of MacPowerInformationService.
+ */
+
+MacPowerInformationService* MacPowerInformationService::sInstance = nullptr;
+
+namespace {
+struct SingletonDestroyer MOZ_FINAL : public nsIObserver
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+};
+
+NS_IMPL_ISUPPORTS1(SingletonDestroyer, nsIObserver)
+
+NS_IMETHODIMP
+SingletonDestroyer::Observe(nsISupports*, const char* aTopic, const PRUnichar*)
+{
+  MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
+  MacPowerInformationService::Shutdown();
+  return NS_OK;
+}
+} // anonymous 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;
+}
+
+void
+MacPowerInformationService::Shutdown()
+{
+  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;
+    }
+
+    if (sIOPSGetTimeRemainingEstimate) {
+      // See if we can get a time estimate.
+      CFTimeInterval estimate = sIOPSGetTimeRemainingEstimate();
+      if (estimate == kIOPSTimeRemainingUnlimited || estimate == kIOPSTimeRemainingUnknown) {
+        remainingTime = kUnknownRemainingTime;
+      } else {
+        remainingTime = estimate;
+      }
+    }
+
+    // Get a battery level estimate. This key is required.
+    int currentCapacity = 0;
+    const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSCurrentCapacityKey));
+    ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, &currentCapacity);
+
+    // 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);
+    }
+
+    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