summaryrefslogtreecommitdiff
path: root/chromium/services/device/generic_sensor/platform_sensor_ambient_light_mac.cc
blob: b99ed16d9faaac759617595cd0faffd2ed6af4db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/device/generic_sensor/platform_sensor_ambient_light_mac.h"

#include <stdint.h>

#include <IOKit/IOMessage.h>

#include "base/bind.h"
#include "device/base/synchronization/shared_memory_seqlock_buffer.h"
#include "services/device/generic_sensor/generic_sensor_consts.h"
#include "services/device/generic_sensor/platform_sensor_provider_mac.h"
#include "services/device/public/cpp/generic_sensor/sensor_traits.h"

namespace {

// Convert the value returned by the ambient light LMU sensor on Mac
// hardware to a lux value.
double LMUvalueToLux(uint64_t raw_value) {
  // Conversion formula from regression.
  // https://bugzilla.mozilla.org/show_bug.cgi?id=793728
  // Let x = raw_value, then
  // lux = -2.978303814*(10^-27)*x^4 + 2.635687683*(10^-19)*x^3 -
  //       3.459747434*(10^-12)*x^2 + 3.905829689*(10^-5)*x - 0.1932594532

  static const long double k4 = pow(10.L, -7);
  static const long double k3 = pow(10.L, -4);
  static const long double k2 = pow(10.L, -2);
  static const long double k1 = pow(10.L, 5);
  long double scaled_value = raw_value / k1;

  long double lux_value =
      (-3 * k4 * pow(scaled_value, 4)) + (2.6 * k3 * pow(scaled_value, 3)) +
      (-3.4 * k2 * pow(scaled_value, 2)) + (3.9 * scaled_value) - 0.19;

  double lux = ceil(static_cast<double>(lux_value));
  return lux > 0 ? lux : 0;
}

}  // namespace

namespace device {

using mojom::SensorType;

enum LmuFunctionIndex {
  kGetSensorReadingID = 0,  // getSensorReading(int *, int *)
};

PlatformSensorAmbientLightMac::PlatformSensorAmbientLightMac(
    mojo::ScopedSharedBufferMapping mapping,
    PlatformSensorProvider* provider)
    : PlatformSensor(SensorType::AMBIENT_LIGHT, std::move(mapping), provider),
      light_sensor_port_(nullptr),
      current_lux_(0.0) {}

PlatformSensorAmbientLightMac::~PlatformSensorAmbientLightMac() = default;

mojom::ReportingMode PlatformSensorAmbientLightMac::GetReportingMode() {
  return mojom::ReportingMode::ON_CHANGE;
}

bool PlatformSensorAmbientLightMac::CheckSensorConfiguration(
    const PlatformSensorConfiguration& configuration) {
  return configuration.frequency() > 0 &&
         configuration.frequency() <=
             SensorTraits<SensorType::AMBIENT_LIGHT>::kMaxAllowedFrequency;
}

PlatformSensorConfiguration
PlatformSensorAmbientLightMac::GetDefaultConfiguration() {
  PlatformSensorConfiguration default_configuration;
  default_configuration.set_frequency(
      SensorTraits<SensorType::AMBIENT_LIGHT>::kDefaultFrequency);
  return default_configuration;
}

void PlatformSensorAmbientLightMac::IOServiceCallback(void* context,
                                                      io_service_t service,
                                                      natural_t message_type,
                                                      void* message_argument) {
  PlatformSensorAmbientLightMac* sensor =
      static_cast<PlatformSensorAmbientLightMac*>(context);
  if (!sensor->ReadAndUpdate()) {
    sensor->NotifySensorError();
    sensor->StopSensor();
  }
}

bool PlatformSensorAmbientLightMac::StartSensor(
    const PlatformSensorConfiguration& configuration) {
  // Tested and verified by riju that the following call works on
  // MacBookPro9,1 : Macbook Pro 15" (Mid 2012 model)
  // MacBookPro10,1 : Macbook Pro 15" (Retina Display, Early 2013 model).
  // MacBookPro10,2 : Macbook Pro 13" (Retina Display, Early 2013 model).
  // MacBookAir5,2 : Macbook Air 13" (Mid 2012 model) (by François Beaufort).
  // MacBookAir6,2 : Macbook Air 13" (Mid 2013 model).
  // Testing plans : please download the code and follow the comments :-
  // https://gist.github.com/riju/74af8c81a665e412d122/
  // and add an entry here about the model and the status returned by the code.

  // Look up a registered IOService object whose class is AppleLMUController.
  light_sensor_service_.reset(IOServiceGetMatchingService(
      kIOMasterPortDefault, IOServiceMatching("AppleLMUController")));

  // Return early if the ambient light sensor is not present.
  if (!light_sensor_service_)
    return false;

  light_sensor_port_.reset(IONotificationPortCreate(kIOMasterPortDefault));
  if (!light_sensor_port_.is_valid())
    return false;

  IONotificationPortSetDispatchQueue(
      light_sensor_port_.get(),
      dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));

  kern_return_t kr = IOServiceAddInterestNotification(
      light_sensor_port_.get(), light_sensor_service_, kIOGeneralInterest,
      IOServiceCallback, this, light_sensor_notification_.InitializeInto());
  if (kr != KERN_SUCCESS)
    return false;

  kr = IOServiceAddInterestNotification(
      light_sensor_port_.get(), light_sensor_service_, kIOBusyInterest,
      IOServiceCallback, this,
      light_sensor_busy_notification_.InitializeInto());
  if (kr != KERN_SUCCESS)
    return false;

  kr = IOServiceOpen(light_sensor_service_, mach_task_self(), 0,
                     light_sensor_object_.InitializeInto());
  if (kr != KERN_SUCCESS)
    return false;

  bool success = ReadAndUpdate();
  if (!success)
    StopSensor();

  return success;
}

void PlatformSensorAmbientLightMac::StopSensor() {
  light_sensor_port_.reset();
  light_sensor_notification_.reset();
  light_sensor_busy_notification_.reset();
  light_sensor_object_.reset();
  light_sensor_service_.reset();
  current_lux_ = 0.0;
}

bool PlatformSensorAmbientLightMac::ReadAndUpdate() {
  uint32_t scalar_output_count = 2;
  uint64_t lux_values[2];
  kern_return_t kr = IOConnectCallMethod(
      light_sensor_object_, LmuFunctionIndex::kGetSensorReadingID, nullptr, 0,
      nullptr, 0, lux_values, &scalar_output_count, nullptr, 0);

  if (kr != KERN_SUCCESS)
    return false;

  uint64_t mean = (lux_values[0] + lux_values[1]) / 2;
  double lux = LMUvalueToLux(mean);
  if (lux == current_lux_)
    return true;
  current_lux_ = lux;

  SensorReading reading;
  reading.als.timestamp =
      (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
  reading.als.value = current_lux_;
  UpdateSharedBufferAndNotifyClients(reading);
  return true;
}

}  // namespace device