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 111385 294643a303c1b9fa4351a793406daf1ec7b8f0cf
parent 111384 7c8b8d36679ff71e4b01e49a65674dfdb3b58253
child 111386 b22c10ecf39093cbb540e3722245b4bbd638d885
push id2248
push userakeybl@mozilla.com
push dateMon, 08 Oct 2012 19:23:44 +0000
treeherdermozilla-aurora@118a3b748323 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersBenWa, mounir
bugs696045
milestone18.0a1
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