/* -*- 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