Merge "Add glue for Gestures logging, and link gestures stack in input reader."
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index 2a00736..6a25db2 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -160,7 +160,7 @@
template <typename _T>
std::string ToString(const _T& t) {
if constexpr (details::ToEmptyString<_T>::value) {
- return "<unimplemented>";
+ return "";
} else if constexpr (std::is_same_v<bool, _T>) {
return t ? "true" : "false";
} else if constexpr (std::is_same_v<char16_t, _T>) {
@@ -176,11 +176,9 @@
return t;
#ifdef HAS_NDK_INTERFACE
} else if constexpr (std::is_same_v<::ndk::SpAIBinder, _T>) {
- std::stringstream ss;
- ss << "binder:" << std::hex << t.get();
- return ss.str();
+ return (t.get() == nullptr) ? "(null)" : "";
} else if constexpr (std::is_same_v<::ndk::ScopedFileDescriptor, _T>) {
- return "fd:" + std::to_string(t.get());
+ return (t.get() == -1) ? "(null)" : "";
#endif
#ifdef HAS_STRING16
} else if constexpr (std::is_same_v<String16, _T>) {
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index fe7a651..0fb64d3 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -27,6 +27,8 @@
// Framework
const float kSdrWhiteNits = 100.0f;
+const float kHlgMaxNits = 1000.0f;
+const float kPqMaxNits = 10000.0f;
struct Color {
union {
@@ -113,9 +115,14 @@
////////////////////////////////////////////////////////////////////////////////
// sRGB transformations
+// NOTE: sRGB has the same color primaries as BT.709, but different transfer
+// function. For this reason, all sRGB transformations here apply to BT.709,
+// except for those concerning transfer functions.
/*
* Calculate the luminance of a linear RGB sRGB pixel, according to IEC 61966-2-1.
+ *
+ * [0.0, 1.0] range in and out.
*/
float srgbLuminance(Color e);
@@ -142,7 +149,9 @@
// Display-P3 transformations
/*
- * Calculated the luminance of a linear RGB P3 pixel, according to EG 432-1.
+ * Calculated the luminance of a linear RGB P3 pixel, according to SMPTE EG 432-1.
+ *
+ * [0.0, 1.0] range in and out.
*/
float p3Luminance(Color e);
@@ -152,6 +161,8 @@
/*
* Calculate the luminance of a linear RGB BT.2100 pixel.
+ *
+ * [0.0, 1.0] range in and out.
*/
float bt2100Luminance(Color e);
@@ -166,23 +177,35 @@
Color bt2100YuvToRgb(Color e_gamma);
/*
- * Convert from scene luminance in nits to HLG.
+ * Convert from scene luminance to HLG.
+ *
+ * [0.0, 1.0] range in and out.
*/
+float hlgOetf(float e);
Color hlgOetf(Color e);
/*
- * Convert from HLG to scene luminance in nits.
+ * Convert from HLG to scene luminance.
+ *
+ * [0.0, 1.0] range in and out.
*/
+float hlgInvOetf(float e_gamma);
Color hlgInvOetf(Color e_gamma);
/*
- * Convert from scene luminance in nits to PQ.
+ * Convert from scene luminance to PQ.
+ *
+ * [0.0, 1.0] range in and out.
*/
+float pqOetf(float e);
Color pqOetf(Color e);
/*
* Convert from PQ to scene luminance in nits.
+ *
+ * [0.0, 1.0] range in and out.
*/
+float pqInvOetf(float e_gamma);
Color pqInvOetf(Color e_gamma);
@@ -230,36 +253,38 @@
Color applyRecovery(Color e, float recovery, float hdr_ratio);
/*
- * Helper for sampling from images.
+ * Helper for sampling from YUV 420 images.
*/
Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
/*
- * Helper for sampling from images.
+ * Helper for sampling from P010 images.
+ *
+ * Expect narrow-range image data for P010.
*/
Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
/*
+ * Sample the image at the provided location, with a weighting based on nearby
+ * pixels and the map scale factor.
+ */
+Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+
+/*
+ * Sample the image at the provided location, with a weighting based on nearby
+ * pixels and the map scale factor.
+ *
+ * Expect narrow-range image data for P010.
+ */
+Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+
+/*
* Sample the recovery value for the map from a given x,y coordinate on a scale
* that is map scale factor larger than the map size.
*/
float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
/*
- * Sample the image Y value at the provided location, with a weighting based on nearby pixels
- * and the map scale factor.
- *
- * Expect narrow-range image data for P010.
- */
-Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
-
-/*
- * Sample the image Y value at the provided location, with a weighting based on nearby pixels
- * and the map scale factor. Assumes narrow-range image data for P010.
- */
-Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
-
-/*
* Convert from Color to RGBA1010102.
*
* Alpha always set to 1.0.
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 4a209ec..f7f3622 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -390,12 +390,15 @@
map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
ColorTransformFn hdrInvOetf = nullptr;
+ float hdr_white_nits = 0.0f;
switch (metadata->transferFunction) {
case JPEGR_TF_HLG:
hdrInvOetf = hlgInvOetf;
+ hdr_white_nits = kHlgMaxNits;
break;
case JPEGR_TF_PQ:
hdrInvOetf = pqInvOetf;
+ hdr_white_nits = kPqMaxNits;
break;
}
@@ -426,7 +429,7 @@
Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
hdr_rgb = hdrGamutConversionFn(hdr_rgb);
- float hdr_y_nits = luminanceFn(hdr_rgb);
+ float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
hdr_y_nits_avg += hdr_y_nits;
if (hdr_y_nits > hdr_y_nits_max) {
@@ -448,13 +451,13 @@
kMapDimensionScaleFactor, x, y);
Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
- float sdr_y_nits = luminanceFn(sdr_rgb);
+ float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
hdr_rgb = hdrGamutConversionFn(hdr_rgb);
- float hdr_y_nits = luminanceFn(hdr_rgb);
+ float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
size_t pixel_idx = x + y * map_width;
reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 6dcbca3..e838f43 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -23,12 +23,14 @@
////////////////////////////////////////////////////////////////////////////////
// sRGB transformations
-static const float kSrgbR = 0.299f, kSrgbG = 0.587f, kSrgbB = 0.114f;
+// See IEC 61966-2-1, Equation F.7.
+static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;
float srgbLuminance(Color e) {
return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b;
}
+// See ECMA TR/98, Section 7.
static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f;
Color srgbYuvToRgb(Color e_gamma) {
@@ -37,15 +39,18 @@
e_gamma.y + kSrgbBCb * e_gamma.u }}};
}
+// See ECMA TR/98, Section 7.
+static const float kSrgbYR = 0.299f, kSrgbYG = 0.587f, kSrgbYB = 0.114f;
static const float kSrgbUR = -0.1687f, kSrgbUG = -0.3313f, kSrgbUB = 0.5f;
static const float kSrgbVR = 0.5f, kSrgbVG = -0.4187f, kSrgbVB = -0.0813f;
Color srgbRgbToYuv(Color e_gamma) {
- return {{{ kSrgbR * e_gamma.r + kSrgbG * e_gamma.g + kSrgbB * e_gamma.b,
+ return {{{ kSrgbYR * e_gamma.r + kSrgbYG * e_gamma.g + kSrgbYB * e_gamma.b,
kSrgbUR * e_gamma.r + kSrgbUG * e_gamma.g + kSrgbUB * e_gamma.b,
kSrgbVR * e_gamma.r + kSrgbVG * e_gamma.g + kSrgbVB * e_gamma.b }}};
}
+// See IEC 61966-2-1, Equations F.5 and F.6.
float srgbInvOetf(float e_gamma) {
if (e_gamma <= 0.04045f) {
return e_gamma / 12.92f;
@@ -64,7 +69,8 @@
////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
-static const float kP3R = 0.22897f, kP3G = 0.69174f, kP3B = 0.07929f;
+// See SMPTE EG 432-1, Table 7-2.
+static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f;
float p3Luminance(Color e) {
return kP3R * e.r + kP3G * e.g + kP3B * e.b;
@@ -74,12 +80,14 @@
////////////////////////////////////////////////////////////////////////////////
// BT.2100 transformations - according to ITU-R BT.2100-2
+// See ITU-R BT.2100-2, Table 5, HLG Reference OOTF
static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f;
float bt2100Luminance(Color e) {
return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b;
}
+// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
Color bt2100RgbToYuv(Color e_gamma) {
@@ -89,9 +97,9 @@
(e_gamma.r - y_gamma) / kBt2100Cr }}};
}
-// Derived from the reverse of bt2100RgbToYuv. The derivation for R and B are
-// pretty straight forward; we just reverse the formulas for U and V above. But
-// deriving the formula for G is a bit more complicated:
+// Derived by inversing bt2100RgbToYuv. The derivation for R and B are pretty
+// straight forward; we just invert the formulas for U and V above. But deriving
+// the formula for G is a bit more complicated:
//
// Start with equation for luminance:
// Y = kBt2100R * R + kBt2100G * G + kBt2100B * B
@@ -119,9 +127,10 @@
e_gamma.y + kBt2100Cb * e_gamma.u }}};
}
+// See ITU-R BT.2100-2, Table 5, HLG Reference OETF.
static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073;
-static float hlgOetf(float e) {
+float hlgOetf(float e) {
if (e <= 1.0f/12.0f) {
return sqrt(3.0f * e);
} else {
@@ -133,7 +142,8 @@
return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
}
-static float hlgInvOetf(float e_gamma) {
+// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
+float hlgInvOetf(float e_gamma) {
if (e_gamma <= 0.5f) {
return pow(e_gamma, 2.0f) / 3.0f;
} else {
@@ -147,13 +157,14 @@
hlgInvOetf(e_gamma.b) }}};
}
+// See ITU-R BT.2100-2, Table 4, Reference PQ OETF.
static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
kPqC3 = 2392.0f / 4096.0f * 32.0f;
-static float pqOetf(float e) {
- if (e < 0.0f) e = 0.0f;
- return pow((kPqC1 + kPqC2 * pow(e / 10000.0f, kPqM1)) / (1 + kPqC3 * pow(e / 10000.0f, kPqM1)),
+float pqOetf(float e) {
+ if (e <= 0.0f) return 0.0f;
+ return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)),
kPqM2);
}
@@ -161,10 +172,18 @@
return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
}
-static float pqInvOetf(float e_gamma) {
- static const float kPqInvOetfCoef = log2(-(pow(kPqM1, 1.0f / kPqM2) - kPqC1)
- / (kPqC3 * pow(kPqM1, 1.0f / kPqM2) - kPqC2));
- return kPqInvOetfCoef / log2(e_gamma * 10000.0f);
+// Derived from the inverse of the Reference PQ OETF.
+static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f,
+ kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f;
+
+float pqInvOetf(float e_gamma) {
+ // This equation blows up if e_gamma is 0.0, and checking on <= 0.0 doesn't
+ // always catch 0.0. So, check on 0.0001, since anything this small will
+ // effectively be crushed to zero anyways.
+ if (e_gamma <= 0.0001f) return 0.0f;
+ return pow((kPqInvA * pow(e_gamma, kPqInvF) - kPqInvB)
+ / (kPqInvC - kPqInvD * pow(e_gamma, kPqInvF)),
+ kPqInvE);
}
Color pqInvOetf(Color e_gamma) {
@@ -217,7 +236,7 @@
// TODO: confirm we always want to convert like this before calculating
// luminance.
ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut) {
- switch (sdr_gamut) {
+ switch (sdr_gamut) {
case JPEGR_COLORGAMUT_BT709:
switch (hdr_gamut) {
case JPEGR_COLORGAMUT_BT709:
@@ -269,13 +288,14 @@
gain = y_hdr / y_sdr;
}
- if (gain < -hdr_ratio) gain = -hdr_ratio;
+ if (gain < (1.0f / hdr_ratio)) gain = 1.0f / hdr_ratio;
if (gain > hdr_ratio) gain = hdr_ratio;
return static_cast<uint8_t>(log2(gain) / log2(hdr_ratio) * 127.5f + 127.5f);
}
static float applyRecovery(float e, float recovery, float hdr_ratio) {
+ if (e <= 0.0f) return 0.0f;
return exp2(log2(e) + recovery * log2(hdr_ratio));
}
@@ -285,45 +305,6 @@
applyRecovery(e.b, recovery, hdr_ratio) }}};
}
-// TODO: do we need something more clever for filtering either the map or images
-// to generate the map?
-
-static size_t clamp(const size_t& val, const size_t& low, const size_t& high) {
- return val < low ? low : (high < val ? high : val);
-}
-
-static float mapUintToFloat(uint8_t map_uint) {
- return (static_cast<float>(map_uint) - 127.5f) / 127.5f;
-}
-
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) {
- float x_map = static_cast<float>(x) / static_cast<float>(map_scale_factor);
- float y_map = static_cast<float>(y) / static_cast<float>(map_scale_factor);
-
- size_t x_lower = static_cast<size_t>(floor(x_map));
- size_t x_upper = x_lower + 1;
- size_t y_lower = static_cast<size_t>(floor(y_map));
- size_t y_upper = y_lower + 1;
-
- x_lower = clamp(x_lower, 0, map->width - 1);
- x_upper = clamp(x_upper, 0, map->width - 1);
- y_lower = clamp(y_lower, 0, map->height - 1);
- y_upper = clamp(y_upper, 0, map->height - 1);
-
- float x_influence = x_map - static_cast<float>(x_lower);
- float y_influence = y_map - static_cast<float>(y_lower);
-
- float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
- float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
- float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
- float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
-
- return e1 * (x_influence + y_influence) / 2.0f
- + e2 * (x_influence + 1.0f - y_influence) / 2.0f
- + e3 * (1.0f - x_influence + y_influence) / 2.0f
- + e4 * (1.0f - x_influence + 1.0f - y_influence) / 2.0f;
-}
-
Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
size_t pixel_count = image->width * image->height;
@@ -382,6 +363,70 @@
return samplePixels(image, map_scale_factor, x, y, getP010Pixel);
}
+// TODO: do we need something more clever for filtering either the map or images
+// to generate the map?
+
+static size_t clamp(const size_t& val, const size_t& low, const size_t& high) {
+ return val < low ? low : (high < val ? high : val);
+}
+
+static float mapUintToFloat(uint8_t map_uint) {
+ return (static_cast<float>(map_uint) - 127.5f) / 127.5f;
+}
+
+static float pythDistance(float x_diff, float y_diff) {
+ return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f));
+}
+
+float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) {
+ float x_map = static_cast<float>(x) / static_cast<float>(map_scale_factor);
+ float y_map = static_cast<float>(y) / static_cast<float>(map_scale_factor);
+
+ size_t x_lower = static_cast<size_t>(floor(x_map));
+ size_t x_upper = x_lower + 1;
+ size_t y_lower = static_cast<size_t>(floor(y_map));
+ size_t y_upper = y_lower + 1;
+
+ x_lower = clamp(x_lower, 0, map->width - 1);
+ x_upper = clamp(x_upper, 0, map->width - 1);
+ y_lower = clamp(y_lower, 0, map->height - 1);
+ y_upper = clamp(y_upper, 0, map->height - 1);
+
+ // Use Shepard's method for inverse distance weighting. For more information:
+ // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
+
+ float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
+ float e1_dist = pythDistance(x_map - static_cast<float>(x_lower),
+ y_map - static_cast<float>(y_lower));
+ if (e1_dist == 0.0f) return e1;
+
+ float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
+ float e2_dist = pythDistance(x_map - static_cast<float>(x_lower),
+ y_map - static_cast<float>(y_upper));
+ if (e2_dist == 0.0f) return e2;
+
+ float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
+ float e3_dist = pythDistance(x_map - static_cast<float>(x_upper),
+ y_map - static_cast<float>(y_lower));
+ if (e3_dist == 0.0f) return e3;
+
+ float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
+ float e4_dist = pythDistance(x_map - static_cast<float>(x_upper),
+ y_map - static_cast<float>(y_upper));
+ if (e4_dist == 0.0f) return e2;
+
+ float e1_weight = 1.0f / e1_dist;
+ float e2_weight = 1.0f / e2_dist;
+ float e3_weight = 1.0f / e3_dist;
+ float e4_weight = 1.0f / e4_dist;
+ float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
+
+ return e1 * (e1_weight / total_weight)
+ + e2 * (e2_weight / total_weight)
+ + e3 * (e3_weight / total_weight)
+ + e4 * (e4_weight / total_weight);
+}
+
uint32_t colorToRgba1010102(Color e_gamma) {
return (0x3ff & static_cast<uint32_t>(e_gamma.r * 1023.0f))
| ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10)
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index 8f37954..b509478 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -26,13 +26,15 @@
test_suites: ["device-tests"],
srcs: [
"recoverymap_test.cpp",
+ "recoverymapmath_test.cpp",
],
shared_libs: [
- "libimage_io",
"libjpeg",
"liblog",
],
static_libs: [
+ "libimage_io",
+ "libgmock",
"libgtest",
"libjpegdecoder",
"libjpegencoder",
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
new file mode 100644
index 0000000..169201c
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
@@ -0,0 +1,882 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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 <cmath>
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include <jpegrecoverymap/recoverymapmath.h>
+
+namespace android::recoverymap {
+
+class RecoveryMapMathTest : public testing::Test {
+public:
+ RecoveryMapMathTest();
+ ~RecoveryMapMathTest();
+
+ float ComparisonEpsilon() { return 1e-4f; }
+ float LuminanceEpsilon() { return 1e-2f; }
+
+ Color Yuv420(uint8_t y, uint8_t u, uint8_t v) {
+ return {{{ static_cast<float>(y) / 255.0f,
+ (static_cast<float>(u) - 128.0f) / 255.0f,
+ (static_cast<float>(v) - 128.0f) / 255.0f }}};
+ }
+
+ Color P010(uint16_t y, uint16_t u, uint16_t v) {
+ return {{{ static_cast<float>(y) / 940.0f,
+ (static_cast<float>(u) - 64.0f) / 940.0f - 0.5f,
+ (static_cast<float>(v) - 64.0f) / 940.0f - 0.5f }}};
+ }
+
+ float Map(uint8_t e) {
+ return (static_cast<float>(e) - 127.5f) / 127.5f;
+ }
+
+ Color ColorMin(Color e1, Color e2) {
+ return {{{ fmin(e1.r, e2.r), fmin(e1.g, e2.g), fmin(e1.b, e2.b) }}};
+ }
+
+ Color ColorMax(Color e1, Color e2) {
+ return {{{ fmax(e1.r, e2.r), fmax(e1.g, e2.g), fmax(e1.b, e2.b) }}};
+ }
+
+ Color RgbBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
+ Color RgbWhite() { return {{{ 1.0f, 1.0f, 1.0f }}}; }
+
+ Color RgbRed() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
+ Color RgbGreen() { return {{{ 0.0f, 1.0f, 0.0f }}}; }
+ Color RgbBlue() { return {{{ 0.0f, 0.0f, 1.0f }}}; }
+
+ Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
+ Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
+
+ Color SrgbYuvRed() { return {{{ 0.299f, -0.1687f, 0.5f }}}; }
+ Color SrgbYuvGreen() { return {{{ 0.587f, -0.3313f, -0.4187f }}}; }
+ Color SrgbYuvBlue() { return {{{ 0.114f, 0.5f, -0.0813f }}}; }
+
+ Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; }
+ Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; }
+ Color Bt2100YuvBlue() { return {{{ 0.0593f, 0.5f, -0.04021f }}}; }
+
+ float SrgbYuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
+ Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
+ Color rgb = srgbInvOetf(rgb_gamma);
+ float luminance_scaled = luminanceFn(rgb);
+ return luminance_scaled * kSdrWhiteNits;
+ }
+
+ float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf,
+ ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn,
+ float scale_factor) {
+ Color rgb_gamma = bt2100YuvToRgb(yuv_gamma);
+ Color rgb = hdrInvOetf(rgb_gamma);
+ rgb = gamutConversionFn(rgb);
+ float luminance_scaled = luminanceFn(rgb);
+ return luminance_scaled * scale_factor;
+ }
+
+ Color Recover(Color yuv_gamma, float recovery, float range_scaling_factor) {
+ Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
+ Color rgb = srgbInvOetf(rgb_gamma);
+ return applyRecovery(rgb, recovery, range_scaling_factor);
+ }
+
+ jpegr_uncompressed_struct Yuv420Image() {
+ static uint8_t pixels[] = {
+ // Y
+ 0x00, 0x10, 0x20, 0x30,
+ 0x01, 0x11, 0x21, 0x31,
+ 0x02, 0x12, 0x22, 0x32,
+ 0x03, 0x13, 0x23, 0x33,
+ // U
+ 0xA0, 0xA1,
+ 0xA2, 0xA3,
+ // V
+ 0xB0, 0xB1,
+ 0xB2, 0xB3,
+ };
+ return { pixels, 4, 4, JPEGR_COLORGAMUT_BT709 };
+ }
+
+ Color (*Yuv420Colors())[4] {
+ static Color colors[4][4] = {
+ {
+ Yuv420(0x00, 0xA0, 0xB0), Yuv420(0x10, 0xA0, 0xB0),
+ Yuv420(0x20, 0xA1, 0xB1), Yuv420(0x30, 0xA1, 0xB1),
+ }, {
+ Yuv420(0x01, 0xA0, 0xB0), Yuv420(0x11, 0xA0, 0xB0),
+ Yuv420(0x21, 0xA1, 0xB1), Yuv420(0x31, 0xA1, 0xB1),
+ }, {
+ Yuv420(0x02, 0xA2, 0xB2), Yuv420(0x12, 0xA2, 0xB2),
+ Yuv420(0x22, 0xA3, 0xB3), Yuv420(0x32, 0xA3, 0xB3),
+ }, {
+ Yuv420(0x03, 0xA2, 0xB2), Yuv420(0x13, 0xA2, 0xB2),
+ Yuv420(0x23, 0xA3, 0xB3), Yuv420(0x33, 0xA3, 0xB3),
+ },
+ };
+ return colors;
+ }
+
+ jpegr_uncompressed_struct P010Image() {
+ static uint16_t pixels[] = {
+ // Y
+ 0x00 << 6, 0x10 << 6, 0x20 << 6, 0x30 << 6,
+ 0x01 << 6, 0x11 << 6, 0x21 << 6, 0x31 << 6,
+ 0x02 << 6, 0x12 << 6, 0x22 << 6, 0x32 << 6,
+ 0x03 << 6, 0x13 << 6, 0x23 << 6, 0x33 << 6,
+ // UV
+ 0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6,
+ 0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6,
+ };
+ return { pixels, 4, 4, JPEGR_COLORGAMUT_BT709 };
+ }
+
+ Color (*P010Colors())[4] {
+ static Color colors[4][4] = {
+ {
+ P010(0x00, 0xA0, 0xB0), P010(0x10, 0xA0, 0xB0),
+ P010(0x20, 0xA1, 0xB1), P010(0x30, 0xA1, 0xB1),
+ }, {
+ P010(0x01, 0xA0, 0xB0), P010(0x11, 0xA0, 0xB0),
+ P010(0x21, 0xA1, 0xB1), P010(0x31, 0xA1, 0xB1),
+ }, {
+ P010(0x02, 0xA2, 0xB2), P010(0x12, 0xA2, 0xB2),
+ P010(0x22, 0xA3, 0xB3), P010(0x32, 0xA3, 0xB3),
+ }, {
+ P010(0x03, 0xA2, 0xB2), P010(0x13, 0xA2, 0xB2),
+ P010(0x23, 0xA3, 0xB3), P010(0x33, 0xA3, 0xB3),
+ },
+ };
+ return colors;
+ }
+
+ jpegr_uncompressed_struct MapImage() {
+ static uint8_t pixels[] = {
+ 0x00, 0x10, 0x20, 0x30,
+ 0x01, 0x11, 0x21, 0x31,
+ 0x02, 0x12, 0x22, 0x32,
+ 0x03, 0x13, 0x23, 0x33,
+ };
+ return { pixels, 4, 4, JPEGR_COLORGAMUT_UNSPECIFIED };
+ }
+
+ float (*MapValues())[4] {
+ static float values[4][4] = {
+ {
+ Map(0x00), Map(0x10), Map(0x20), Map(0x30),
+ }, {
+ Map(0x01), Map(0x11), Map(0x21), Map(0x31),
+ }, {
+ Map(0x02), Map(0x12), Map(0x22), Map(0x32),
+ }, {
+ Map(0x03), Map(0x13), Map(0x23), Map(0x33),
+ },
+ };
+ return values;
+ }
+
+protected:
+ virtual void SetUp();
+ virtual void TearDown();
+};
+
+RecoveryMapMathTest::RecoveryMapMathTest() {}
+RecoveryMapMathTest::~RecoveryMapMathTest() {}
+
+void RecoveryMapMathTest::SetUp() {}
+void RecoveryMapMathTest::TearDown() {}
+
+#define EXPECT_RGB_EQ(e1, e2) \
+ EXPECT_FLOAT_EQ((e1).r, (e2).r); \
+ EXPECT_FLOAT_EQ((e1).g, (e2).g); \
+ EXPECT_FLOAT_EQ((e1).b, (e2).b)
+
+#define EXPECT_RGB_NEAR(e1, e2) \
+ EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon()); \
+ EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon()); \
+ EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon())
+
+#define EXPECT_RGB_CLOSE(e1, e2) \
+ EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon() * 10.0f); \
+ EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \
+ EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f)
+
+#define EXPECT_YUV_EQ(e1, e2) \
+ EXPECT_FLOAT_EQ((e1).y, (e2).y); \
+ EXPECT_FLOAT_EQ((e1).u, (e2).u); \
+ EXPECT_FLOAT_EQ((e1).v, (e2).v)
+
+#define EXPECT_YUV_NEAR(e1, e2) \
+ EXPECT_NEAR((e1).y, (e2).y, ComparisonEpsilon()); \
+ EXPECT_NEAR((e1).u, (e2).u, ComparisonEpsilon()); \
+ EXPECT_NEAR((e1).v, (e2).v, ComparisonEpsilon())
+
+#define EXPECT_YUV_BETWEEN(e, min, max) \
+ EXPECT_THAT((e).y, testing::AllOf(testing::Ge((min).y), testing::Le((max).y))); \
+ EXPECT_THAT((e).u, testing::AllOf(testing::Ge((min).u), testing::Le((max).u))); \
+ EXPECT_THAT((e).v, testing::AllOf(testing::Ge((min).v), testing::Le((max).v)))
+
+// TODO: a bunch of these tests can be parameterized.
+
+TEST_F(RecoveryMapMathTest, ColorConstruct) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ EXPECT_FLOAT_EQ(e1.r, 0.1f);
+ EXPECT_FLOAT_EQ(e1.g, 0.2f);
+ EXPECT_FLOAT_EQ(e1.b, 0.3f);
+
+ EXPECT_FLOAT_EQ(e1.y, 0.1f);
+ EXPECT_FLOAT_EQ(e1.u, 0.2f);
+ EXPECT_FLOAT_EQ(e1.v, 0.3f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorAddColor) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 + e1;
+ EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
+
+ e2 += e1;
+ EXPECT_FLOAT_EQ(e2.r, e1.r * 3.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g * 3.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorAddFloat) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 + 0.1f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g + 0.1f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b + 0.1f);
+
+ e2 += 0.1f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r + 0.2f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g + 0.2f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorSubtractColor) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 - e1;
+ EXPECT_FLOAT_EQ(e2.r, 0.0f);
+ EXPECT_FLOAT_EQ(e2.g, 0.0f);
+ EXPECT_FLOAT_EQ(e2.b, 0.0f);
+
+ e2 -= e1;
+ EXPECT_FLOAT_EQ(e2.r, -e1.r);
+ EXPECT_FLOAT_EQ(e2.g, -e1.g);
+ EXPECT_FLOAT_EQ(e2.b, -e1.b);
+}
+
+TEST_F(RecoveryMapMathTest, ColorSubtractFloat) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 - 0.1f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g - 0.1f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b - 0.1f);
+
+ e2 -= 0.1f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r - 0.2f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g - 0.2f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorMultiplyFloat) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 * 2.0f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
+
+ e2 *= 2.0f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r * 4.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g * 4.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorDivideFloat) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 / 2.0f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g / 2.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b / 2.0f);
+
+ e2 /= 2.0f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r / 4.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g / 4.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f);
+}
+
+TEST_F(RecoveryMapMathTest, SrgbLuminance) {
+ EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f);
+ EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f);
+ EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f);
+ EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.7152f);
+ EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f);
+}
+
+TEST_F(RecoveryMapMathTest, SrgbYuvToRgb) {
+ Color rgb_black = srgbYuvToRgb(YuvBlack());
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = srgbYuvToRgb(YuvWhite());
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = srgbYuvToRgb(SrgbYuvRed());
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = srgbYuvToRgb(SrgbYuvGreen());
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = srgbYuvToRgb(SrgbYuvBlue());
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(RecoveryMapMathTest, SrgbRgbToYuv) {
+ Color yuv_black = srgbRgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv_black, YuvBlack());
+
+ Color yuv_white = srgbRgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv_white, YuvWhite());
+
+ Color yuv_r = srgbRgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv_r, SrgbYuvRed());
+
+ Color yuv_g = srgbRgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv_g, SrgbYuvGreen());
+
+ Color yuv_b = srgbRgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue());
+}
+
+TEST_F(RecoveryMapMathTest, SrgbRgbYuvRoundtrip) {
+ Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack()));
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = srgbYuvToRgb(srgbRgbToYuv(RgbWhite()));
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = srgbYuvToRgb(srgbRgbToYuv(RgbRed()));
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = srgbYuvToRgb(srgbRgbToYuv(RgbGreen()));
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = srgbYuvToRgb(srgbRgbToYuv(RgbBlue()));
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(RecoveryMapMathTest, SrgbTransferFunction) {
+ EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f);
+ EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon());
+ EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon());
+ EXPECT_NEAR(srgbInvOetf(0.5f), 0.21404f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f);
+}
+
+TEST_F(RecoveryMapMathTest, P3Luminance) {
+ EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f);
+ EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f);
+ EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f);
+ EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.72160f);
+ EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f);
+}
+
+TEST_F(RecoveryMapMathTest, Bt2100Luminance) {
+ EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f);
+ EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f);
+ EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f);
+ EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.6780f);
+ EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f);
+}
+
+TEST_F(RecoveryMapMathTest, Bt2100YuvToRgb) {
+ Color rgb_black = bt2100YuvToRgb(YuvBlack());
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = bt2100YuvToRgb(YuvWhite());
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = bt2100YuvToRgb(Bt2100YuvRed());
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = bt2100YuvToRgb(Bt2100YuvGreen());
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = bt2100YuvToRgb(Bt2100YuvBlue());
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(RecoveryMapMathTest, Bt2100RgbToYuv) {
+ Color yuv_black = bt2100RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv_black, YuvBlack());
+
+ Color yuv_white = bt2100RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv_white, YuvWhite());
+
+ Color yuv_r = bt2100RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv_r, Bt2100YuvRed());
+
+ Color yuv_g = bt2100RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv_g, Bt2100YuvGreen());
+
+ Color yuv_b = bt2100RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue());
+}
+
+TEST_F(RecoveryMapMathTest, Bt2100RgbYuvRoundtrip) {
+ Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack()));
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = bt2100YuvToRgb(bt2100RgbToYuv(RgbWhite()));
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = bt2100YuvToRgb(bt2100RgbToYuv(RgbRed()));
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = bt2100YuvToRgb(bt2100RgbToYuv(RgbGreen()));
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlue()));
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(RecoveryMapMathTest, HlgOetf) {
+ EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f);
+ EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f);
+
+ Color e = {{{ 0.04167f, 0.08333f, 0.5f }}};
+ Color e_gamma = {{{ 0.35357f, 0.5f, 0.87164f }}};
+ EXPECT_RGB_NEAR(hlgOetf(e), e_gamma);
+}
+
+TEST_F(RecoveryMapMathTest, HlgInvOetf) {
+ EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f);
+ EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f);
+
+ Color e_gamma = {{{ 0.25f, 0.5f, 0.75f }}};
+ Color e = {{{ 0.02083f, 0.08333f, 0.26496f }}};
+ EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e);
+}
+
+TEST_F(RecoveryMapMathTest, HlgTransferFunctionRoundtrip) {
+ EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f);
+ EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgInvOetf(hlgOetf(0.5f)), 0.5f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f);
+}
+
+TEST_F(RecoveryMapMathTest, PqOetf) {
+ EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f);
+ EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon());
+ EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon());
+ EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f);
+
+ Color e = {{{ 0.01f, 0.5f, 0.99f }}};
+ Color e_gamma = {{{ 0.50808f, 0.92655f, 0.99895f }}};
+ EXPECT_RGB_NEAR(pqOetf(e), e_gamma);
+}
+
+TEST_F(RecoveryMapMathTest, PqInvOetf) {
+ EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f);
+ EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon());
+ EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon());
+ EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f);
+
+ Color e_gamma = {{{ 0.01f, 0.5f, 0.99f }}};
+ Color e = {{{ 2.31017e-7f, 0.00922f, 0.90903f }}};
+ EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
+}
+
+TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) {
+ EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
+ EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());
+ EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon());
+ EXPECT_NEAR(pqInvOetf(pqOetf(0.99f)), 0.99f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorConversionLookup) {
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_UNSPECIFIED),
+ nullptr);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT709),
+ identityConversion);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_P3),
+ p3ToBt709);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT2100),
+ bt2100ToBt709);
+
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_UNSPECIFIED),
+ nullptr);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_BT709),
+ bt709ToP3);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_P3),
+ identityConversion);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_BT2100),
+ bt2100ToP3);
+
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_UNSPECIFIED),
+ nullptr);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_BT709),
+ bt709ToBt2100);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_P3),
+ p3ToBt2100);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_BT2100),
+ identityConversion);
+
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_UNSPECIFIED),
+ nullptr);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT709),
+ nullptr);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_P3),
+ nullptr);
+ EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT2100),
+ nullptr);
+}
+
+TEST_F(RecoveryMapMathTest, EncodeRecovery) {
+ EXPECT_EQ(encodeRecovery(0.0f, 0.0f, 4.0f), 127);
+ EXPECT_EQ(encodeRecovery(0.0f, 1.0f, 4.0f), 127);
+ EXPECT_EQ(encodeRecovery(1.0f, 0.0f, 4.0f), 0);
+ EXPECT_EQ(encodeRecovery(0.5f, 0.0f, 4.0f), 0);
+
+ EXPECT_EQ(encodeRecovery(1.0f, 1.0f, 4.0f), 127);
+ EXPECT_EQ(encodeRecovery(1.0f, 4.0f, 4.0f), 255);
+ EXPECT_EQ(encodeRecovery(1.0f, 5.0f, 4.0f), 255);
+ EXPECT_EQ(encodeRecovery(4.0f, 1.0f, 4.0f), 0);
+ EXPECT_EQ(encodeRecovery(4.0f, 0.5f, 4.0f), 0);
+ EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 4.0f), 191);
+ EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 4.0f), 63);
+
+ EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 2.0f), 255);
+ EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 2.0f), 0);
+ EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, 2.0f), 191);
+ EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, 2.0f), 63);
+
+ EXPECT_EQ(encodeRecovery(1.0f, 8.0f, 8.0f), 255);
+ EXPECT_EQ(encodeRecovery(8.0f, 1.0f, 8.0f), 0);
+ EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, 8.0f), 191);
+ EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, 8.0f), 63);
+}
+
+TEST_F(RecoveryMapMathTest, ApplyRecovery) {
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), -1.0f, 4.0f), RgbBlack());
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, 4.0f), RgbBlack());
+ EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, 4.0f), RgbBlack());
+
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 4.0f), RgbWhite() / 4.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 4.0f), RgbWhite() / 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 4.0f), RgbWhite());
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 4.0f), RgbWhite() * 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 4.0f), RgbWhite() * 4.0f);
+
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 2.0f), RgbWhite() / 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 2.0f), RgbWhite() / 1.41421f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 2.0f), RgbWhite());
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 2.0f), RgbWhite() * 1.41421f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 2.0f), RgbWhite() * 2.0f);
+
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 8.0f), RgbWhite() / 8.0f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 8.0f), RgbWhite() / 2.82843f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 8.0f), RgbWhite());
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 8.0f), RgbWhite() * 2.82843f);
+ EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 8.0f), RgbWhite() * 8.0f);
+
+ Color e = {{{ 0.0f, 0.5f, 1.0f }}};
+
+ EXPECT_RGB_NEAR(applyRecovery(e, -1.0f, 4.0f), e / 4.0f);
+ EXPECT_RGB_NEAR(applyRecovery(e, -0.5f, 4.0f), e / 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, 4.0f), e);
+ EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, 4.0f), e * 2.0f);
+ EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, 4.0f), e * 4.0f);
+}
+
+TEST_F(RecoveryMapMathTest, GetYuv420Pixel) {
+ jpegr_uncompressed_struct image = Yuv420Image();
+ Color (*colors)[4] = Yuv420Colors();
+
+ for (size_t y = 0; y < 4; ++y) {
+ for (size_t x = 0; x < 4; ++x) {
+ EXPECT_YUV_NEAR(getYuv420Pixel(&image, x, y), colors[y][x]);
+ }
+ }
+}
+
+TEST_F(RecoveryMapMathTest, GetP010Pixel) {
+ jpegr_uncompressed_struct image = P010Image();
+ Color (*colors)[4] = P010Colors();
+
+ for (size_t y = 0; y < 4; ++y) {
+ for (size_t x = 0; x < 4; ++x) {
+ EXPECT_YUV_NEAR(getP010Pixel(&image, x, y), colors[y][x]);
+ }
+ }
+}
+
+TEST_F(RecoveryMapMathTest, SampleYuv420) {
+ jpegr_uncompressed_struct image = Yuv420Image();
+ Color (*colors)[4] = Yuv420Colors();
+
+ static const size_t kMapScaleFactor = 2;
+ for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
+ for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
+ Color min = {{{ 1.0f, 1.0f, 1.0f }}};
+ Color max = {{{ -1.0f, -1.0f, -1.0f }}};
+
+ for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
+ for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
+ Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
+ min = ColorMin(min, e);
+ max = ColorMax(max, e);
+ }
+ }
+
+ // Instead of reimplementing the sampling algorithm, confirm that the
+ // sample output is within the range of the min and max of the nearest
+ // points.
+ EXPECT_YUV_BETWEEN(sampleYuv420(&image, kMapScaleFactor, x, y), min, max);
+ }
+ }
+}
+
+TEST_F(RecoveryMapMathTest, SampleP010) {
+ jpegr_uncompressed_struct image = P010Image();
+ Color (*colors)[4] = P010Colors();
+
+ static const size_t kMapScaleFactor = 2;
+ for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
+ for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
+ Color min = {{{ 1.0f, 1.0f, 1.0f }}};
+ Color max = {{{ -1.0f, -1.0f, -1.0f }}};
+
+ for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
+ for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
+ Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
+ min = ColorMin(min, e);
+ max = ColorMax(max, e);
+ }
+ }
+
+ // Instead of reimplementing the sampling algorithm, confirm that the
+ // sample output is within the range of the min and max of the nearest
+ // points.
+ EXPECT_YUV_BETWEEN(sampleP010(&image, kMapScaleFactor, x, y), min, max);
+ }
+ }
+}
+
+TEST_F(RecoveryMapMathTest, SampleMap) {
+ jpegr_uncompressed_struct image = MapImage();
+ float (*values)[4] = MapValues();
+
+ static const size_t kMapScaleFactor = 2;
+ for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) {
+ for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) {
+ size_t x_base = x / kMapScaleFactor;
+ size_t y_base = y / kMapScaleFactor;
+
+ float min = 1.0f;
+ float max = -1.0f;
+
+ min = fmin(min, values[y_base][x_base]);
+ max = fmax(max, values[y_base][x_base]);
+ if (y_base + 1 < 4) {
+ min = fmin(min, values[y_base + 1][x_base]);
+ max = fmax(max, values[y_base + 1][x_base]);
+ }
+ if (x_base + 1 < 4) {
+ min = fmin(min, values[y_base][x_base + 1]);
+ max = fmax(max, values[y_base][x_base + 1]);
+ }
+ if (y_base + 1 < 4 && x_base + 1 < 4) {
+ min = fmin(min, values[y_base + 1][x_base + 1]);
+ max = fmax(max, values[y_base + 1][x_base + 1]);
+ }
+
+ // Instead of reimplementing the sampling algorithm, confirm that the
+ // sample output is within the range of the min and max of the nearest
+ // points.
+ EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y),
+ testing::AllOf(testing::Ge(min), testing::Le(max)));
+ }
+ }
+}
+
+TEST_F(RecoveryMapMathTest, ColorToRgba1010102) {
+ EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30);
+ EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF);
+ EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff);
+ EXPECT_EQ(colorToRgba1010102(RgbGreen()), 0x3 << 30 | 0x3ff << 10);
+ EXPECT_EQ(colorToRgba1010102(RgbBlue()), 0x3 << 30 | 0x3ff << 20);
+
+ Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}};
+ EXPECT_EQ(colorToRgba1010102(e_gamma),
+ 0x3 << 30
+ | static_cast<uint32_t>(0.1f * static_cast<float>(0x3ff))
+ | static_cast<uint32_t>(0.2f * static_cast<float>(0x3ff)) << 10
+ | static_cast<uint32_t>(0.3f * static_cast<float>(0x3ff)) << 20);
+}
+
+TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgb) {
+ EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance),
+ 0.0f);
+ EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance),
+ kSdrWhiteNits);
+ EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), srgbLuminance),
+ srgbLuminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
+ EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), srgbLuminance),
+ srgbLuminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
+ EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), srgbLuminance),
+ srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
+}
+
+TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbP3) {
+ EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance),
+ 0.0f);
+ EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance),
+ kSdrWhiteNits);
+ EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance),
+ p3Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
+ EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), p3Luminance),
+ p3Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
+ EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), p3Luminance),
+ p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
+}
+
+TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbBt2100) {
+ EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance),
+ 0.0f);
+ EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance),
+ kSdrWhiteNits);
+ EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), bt2100Luminance),
+ bt2100Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
+ EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), bt2100Luminance),
+ bt2100Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
+ EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), bt2100Luminance),
+ bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
+}
+
+TEST_F(RecoveryMapMathTest, GenerateMapLuminanceHlg) {
+ EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion,
+ bt2100Luminance, kHlgMaxNits),
+ 0.0f);
+ EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion,
+ bt2100Luminance, kHlgMaxNits),
+ kHlgMaxNits);
+ EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion,
+ bt2100Luminance, kHlgMaxNits),
+ bt2100Luminance(RgbRed()) * kHlgMaxNits, LuminanceEpsilon());
+ EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), hlgInvOetf, identityConversion,
+ bt2100Luminance, kHlgMaxNits),
+ bt2100Luminance(RgbGreen()) * kHlgMaxNits, LuminanceEpsilon());
+ EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion,
+ bt2100Luminance, kHlgMaxNits),
+ bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon());
+}
+
+TEST_F(RecoveryMapMathTest, GenerateMapLuminancePq) {
+ EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion,
+ bt2100Luminance, kPqMaxNits),
+ 0.0f);
+ EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion,
+ bt2100Luminance, kPqMaxNits),
+ kPqMaxNits);
+ EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion,
+ bt2100Luminance, kPqMaxNits),
+ bt2100Luminance(RgbRed()) * kPqMaxNits, LuminanceEpsilon());
+ EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion,
+ bt2100Luminance, kPqMaxNits),
+ bt2100Luminance(RgbGreen()) * kPqMaxNits, LuminanceEpsilon());
+ EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion,
+ bt2100Luminance, kPqMaxNits),
+ bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon());
+}
+
+//Color Recover(Color yuv_gamma, float recovery, float range_scaling_factor) {
+TEST_F(RecoveryMapMathTest, ApplyMap) {
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, 8.0f),
+ RgbWhite() * 8.0f);
+ EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, 8.0f),
+ RgbBlack());
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, 8.0f),
+ RgbRed() * 8.0f);
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, 8.0f),
+ RgbGreen() * 8.0f);
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, 8.0f),
+ RgbBlue() * 8.0f);
+
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, 8.0f),
+ RgbWhite() * sqrt(8.0f));
+ EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, 8.0f),
+ RgbBlack());
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, 8.0f),
+ RgbRed() * sqrt(8.0f));
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, 8.0f),
+ RgbGreen() * sqrt(8.0f));
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, 8.0f),
+ RgbBlue() * sqrt(8.0f));
+
+ EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, 8.0f),
+ RgbWhite());
+ EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, 8.0f),
+ RgbBlack());
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, 8.0f),
+ RgbRed());
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, 8.0f),
+ RgbGreen());
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, 8.0f),
+ RgbBlue());
+
+ EXPECT_RGB_EQ(Recover(YuvWhite(), -0.5f, 8.0f),
+ RgbWhite() / sqrt(8.0f));
+ EXPECT_RGB_EQ(Recover(YuvBlack(), -0.5f, 8.0f),
+ RgbBlack());
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -0.5f, 8.0f),
+ RgbRed() / sqrt(8.0f));
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -0.5f, 8.0f),
+ RgbGreen() / sqrt(8.0f));
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -0.5f, 8.0f),
+ RgbBlue() / sqrt(8.0f));
+
+ EXPECT_RGB_EQ(Recover(YuvWhite(), -1.0f, 8.0f),
+ RgbWhite() / 8.0f);
+ EXPECT_RGB_EQ(Recover(YuvBlack(), -1.0f, 8.0f),
+ RgbBlack());
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -1.0f, 8.0f),
+ RgbRed() / 8.0f);
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -1.0f, 8.0f),
+ RgbGreen() / 8.0f);
+ EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -1.0f, 8.0f),
+ RgbBlue() / 8.0f);
+}
+
+} // namespace android::recoverymap
diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp
index 78f692b..2278d39 100644
--- a/libs/sensor/ISensorServer.cpp
+++ b/libs/sensor/ISensorServer.cpp
@@ -42,6 +42,7 @@
GET_DYNAMIC_SENSOR_LIST,
CREATE_SENSOR_DIRECT_CONNECTION,
SET_OPERATION_PARAMETER,
+ GET_RUNTIME_SENSOR_LIST,
};
class BpSensorServer : public BpInterface<ISensorServer>
@@ -90,6 +91,25 @@
return v;
}
+ virtual Vector<Sensor> getRuntimeSensorList(const String16& opPackageName, int deviceId)
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
+ data.writeString16(opPackageName);
+ data.writeInt32(deviceId);
+ remote()->transact(GET_RUNTIME_SENSOR_LIST, data, &reply);
+ Sensor s;
+ Vector<Sensor> v;
+ uint32_t n = reply.readUint32();
+ v.setCapacity(n);
+ while (n) {
+ n--;
+ reply.read(s);
+ v.add(s);
+ }
+ return v;
+ }
+
virtual sp<ISensorEventConnection> createSensorEventConnection(const String8& packageName,
int mode, const String16& opPackageName, const String16& attributionTag)
{
@@ -194,6 +214,18 @@
}
return NO_ERROR;
}
+ case GET_RUNTIME_SENSOR_LIST: {
+ CHECK_INTERFACE(ISensorServer, data, reply);
+ const String16& opPackageName = data.readString16();
+ const int deviceId = data.readInt32();
+ Vector<Sensor> v(getRuntimeSensorList(opPackageName, deviceId));
+ size_t n = v.size();
+ reply->writeUint32(static_cast<uint32_t>(n));
+ for (size_t i = 0; i < n; i++) {
+ reply->write(v[i]);
+ }
+ return NO_ERROR;
+ }
case CREATE_SENSOR_DIRECT_CONNECTION: {
CHECK_INTERFACE(ISensorServer, data, reply);
const String16& opPackageName = data.readString16();
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 0ba9704..2748276 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -201,6 +201,19 @@
return static_cast<ssize_t>(count);
}
+ssize_t SensorManager::getRuntimeSensorList(int deviceId, Vector<Sensor>& runtimeSensors) {
+ Mutex::Autolock _l(mLock);
+ status_t err = assertStateLocked();
+ if (err < 0) {
+ return static_cast<ssize_t>(err);
+ }
+
+ runtimeSensors = mSensorServer->getRuntimeSensorList(mOpPackageName, deviceId);
+ size_t count = runtimeSensors.size();
+
+ return static_cast<ssize_t>(count);
+}
+
ssize_t SensorManager::getDynamicSensorList(Sensor const* const** list) {
Mutex::Autolock _l(mLock);
status_t err = assertStateLocked();
diff --git a/libs/sensor/include/sensor/ISensorServer.h b/libs/sensor/include/sensor/ISensorServer.h
index ce5c672..3295196 100644
--- a/libs/sensor/include/sensor/ISensorServer.h
+++ b/libs/sensor/include/sensor/ISensorServer.h
@@ -43,6 +43,7 @@
virtual Vector<Sensor> getSensorList(const String16& opPackageName) = 0;
virtual Vector<Sensor> getDynamicSensorList(const String16& opPackageName) = 0;
+ virtual Vector<Sensor> getRuntimeSensorList(const String16& opPackageName, int deviceId) = 0;
virtual sp<ISensorEventConnection> createSensorEventConnection(const String8& packageName,
int mode, const String16& opPackageName, const String16& attributionTag) = 0;
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index 8d0a8a4..0798da2 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -59,6 +59,7 @@
ssize_t getSensorList(Sensor const* const** list);
ssize_t getDynamicSensorList(Vector<Sensor>& list);
ssize_t getDynamicSensorList(Sensor const* const** list);
+ ssize_t getRuntimeSensorList(int deviceId, Vector<Sensor>& list);
Sensor const* getDefaultSensor(int type);
sp<SensorEventQueue> createEventQueue(
String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 8b752b6..2e5bec9 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -41,6 +41,8 @@
"BlockingQueue_test.cpp",
"EventHub_test.cpp",
"FakeEventHub.cpp",
+ "FakeInputReaderPolicy.cpp",
+ "FakePointerController.cpp",
"FocusResolver_test.cpp",
"InputProcessor_test.cpp",
"InputProcessorConverter_test.cpp",
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
new file mode 100644
index 0000000..5c6a1b8
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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 "FakeInputReaderPolicy.h"
+
+#include <android-base/thread_annotations.h>
+#include <gtest/gtest.h>
+
+#include "TestConstants.h"
+
+namespace android {
+
+void FakeInputReaderPolicy::assertInputDevicesChanged() {
+ waitForInputDevices([](bool devicesChanged) {
+ if (!devicesChanged) {
+ FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called.";
+ }
+ });
+}
+
+void FakeInputReaderPolicy::assertInputDevicesNotChanged() {
+ waitForInputDevices([](bool devicesChanged) {
+ if (devicesChanged) {
+ FAIL() << "Expected notifyInputDevicesChanged() to not be called.";
+ }
+ });
+}
+
+void FakeInputReaderPolicy::assertStylusGestureNotified(int32_t deviceId) {
+ std::scoped_lock lock(mLock);
+ ASSERT_TRUE(mStylusGestureNotified);
+ ASSERT_EQ(deviceId, *mStylusGestureNotified);
+ mStylusGestureNotified.reset();
+}
+
+void FakeInputReaderPolicy::assertStylusGestureNotNotified() {
+ std::scoped_lock lock(mLock);
+ ASSERT_FALSE(mStylusGestureNotified);
+}
+
+void FakeInputReaderPolicy::clearViewports() {
+ mViewports.clear();
+ mConfig.setDisplayViewports(mViewports);
+}
+
+std::optional<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByUniqueId(
+ const std::string& uniqueId) const {
+ return mConfig.getDisplayViewportByUniqueId(uniqueId);
+}
+std::optional<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByType(
+ ViewportType type) const {
+ return mConfig.getDisplayViewportByType(type);
+}
+
+std::optional<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByPort(
+ uint8_t displayPort) const {
+ return mConfig.getDisplayViewportByPort(displayPort);
+}
+
+void FakeInputReaderPolicy::addDisplayViewport(DisplayViewport viewport) {
+ mViewports.push_back(std::move(viewport));
+ mConfig.setDisplayViewports(mViewports);
+}
+
+void FakeInputReaderPolicy::addDisplayViewport(int32_t displayId, int32_t width, int32_t height,
+ int32_t orientation, bool isActive,
+ const std::string& uniqueId,
+ std::optional<uint8_t> physicalPort,
+ ViewportType type) {
+ const bool isRotated =
+ (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270);
+ DisplayViewport v;
+ v.displayId = displayId;
+ v.orientation = orientation;
+ v.logicalLeft = 0;
+ v.logicalTop = 0;
+ v.logicalRight = isRotated ? height : width;
+ v.logicalBottom = isRotated ? width : height;
+ v.physicalLeft = 0;
+ v.physicalTop = 0;
+ v.physicalRight = isRotated ? height : width;
+ v.physicalBottom = isRotated ? width : height;
+ v.deviceWidth = isRotated ? height : width;
+ v.deviceHeight = isRotated ? width : height;
+ v.isActive = isActive;
+ v.uniqueId = uniqueId;
+ v.physicalPort = physicalPort;
+ v.type = type;
+
+ addDisplayViewport(v);
+}
+
+bool FakeInputReaderPolicy::updateViewport(const DisplayViewport& viewport) {
+ size_t count = mViewports.size();
+ for (size_t i = 0; i < count; i++) {
+ const DisplayViewport& currentViewport = mViewports[i];
+ if (currentViewport.displayId == viewport.displayId) {
+ mViewports[i] = viewport;
+ mConfig.setDisplayViewports(mViewports);
+ return true;
+ }
+ }
+ // no viewport found.
+ return false;
+}
+
+void FakeInputReaderPolicy::addExcludedDeviceName(const std::string& deviceName) {
+ mConfig.excludedDeviceNames.push_back(deviceName);
+}
+
+void FakeInputReaderPolicy::addInputPortAssociation(const std::string& inputPort,
+ uint8_t displayPort) {
+ mConfig.portAssociations.insert({inputPort, displayPort});
+}
+
+void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId,
+ const std::string& displayUniqueId) {
+ mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
+}
+
+void FakeInputReaderPolicy::addDisabledDevice(int32_t deviceId) {
+ mConfig.disabledDevices.insert(deviceId);
+}
+
+void FakeInputReaderPolicy::removeDisabledDevice(int32_t deviceId) {
+ mConfig.disabledDevices.erase(deviceId);
+}
+
+void FakeInputReaderPolicy::setPointerController(
+ std::shared_ptr<FakePointerController> controller) {
+ mPointerController = std::move(controller);
+}
+
+const InputReaderConfiguration* FakeInputReaderPolicy::getReaderConfiguration() const {
+ return &mConfig;
+}
+
+const std::vector<InputDeviceInfo>& FakeInputReaderPolicy::getInputDevices() const {
+ return mInputDevices;
+}
+
+TouchAffineTransformation FakeInputReaderPolicy::getTouchAffineTransformation(
+ const std::string& inputDeviceDescriptor, int32_t surfaceRotation) {
+ return transform;
+}
+
+void FakeInputReaderPolicy::setTouchAffineTransformation(const TouchAffineTransformation t) {
+ transform = t;
+}
+
+PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(bool enabled) {
+ mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++};
+ return mConfig.pointerCaptureRequest;
+}
+
+void FakeInputReaderPolicy::setShowTouches(bool enabled) {
+ mConfig.showTouches = enabled;
+}
+
+void FakeInputReaderPolicy::setDefaultPointerDisplayId(int32_t pointerDisplayId) {
+ mConfig.defaultPointerDisplayId = pointerDisplayId;
+}
+
+void FakeInputReaderPolicy::setPointerGestureEnabled(bool enabled) {
+ mConfig.pointerGesturesEnabled = enabled;
+}
+
+float FakeInputReaderPolicy::getPointerGestureMovementSpeedRatio() {
+ return mConfig.pointerGestureMovementSpeedRatio;
+}
+
+float FakeInputReaderPolicy::getPointerGestureZoomSpeedRatio() {
+ return mConfig.pointerGestureZoomSpeedRatio;
+}
+
+void FakeInputReaderPolicy::setVelocityControlParams(const VelocityControlParameters& params) {
+ mConfig.pointerVelocityControlParameters = params;
+ mConfig.wheelVelocityControlParameters = params;
+}
+
+void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) {
+ *outConfig = mConfig;
+}
+
+std::shared_ptr<PointerControllerInterface> FakeInputReaderPolicy::obtainPointerController(
+ int32_t /*deviceId*/) {
+ return mPointerController;
+}
+
+void FakeInputReaderPolicy::notifyInputDevicesChanged(
+ const std::vector<InputDeviceInfo>& inputDevices) {
+ std::scoped_lock<std::mutex> lock(mLock);
+ mInputDevices = inputDevices;
+ mInputDevicesChanged = true;
+ mDevicesChangedCondition.notify_all();
+}
+
+std::shared_ptr<KeyCharacterMap> FakeInputReaderPolicy::getKeyboardLayoutOverlay(
+ const InputDeviceIdentifier&) {
+ return nullptr;
+}
+
+std::string FakeInputReaderPolicy::getDeviceAlias(const InputDeviceIdentifier&) {
+ return "";
+}
+
+void FakeInputReaderPolicy::waitForInputDevices(std::function<void(bool)> processDevicesChanged) {
+ std::unique_lock<std::mutex> lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+
+ const bool devicesChanged =
+ mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
+ return mInputDevicesChanged;
+ });
+ ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
+ mInputDevicesChanged = false;
+}
+
+void FakeInputReaderPolicy::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) {
+ std::scoped_lock<std::mutex> lock(mLock);
+ mStylusGestureNotified = deviceId;
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
new file mode 100644
index 0000000..65fe08f
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <InputDevice.h>
+#include <InputReaderBase.h>
+
+#include "FakePointerController.h"
+#include "input/DisplayViewport.h"
+#include "input/InputDevice.h"
+
+namespace android {
+
+class FakeInputReaderPolicy : public InputReaderPolicyInterface {
+protected:
+ virtual ~FakeInputReaderPolicy() {}
+
+public:
+ FakeInputReaderPolicy() {}
+
+ void assertInputDevicesChanged();
+ void assertInputDevicesNotChanged();
+ void assertStylusGestureNotified(int32_t deviceId);
+ void assertStylusGestureNotNotified();
+
+ virtual void clearViewports();
+ std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const;
+ std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const;
+ std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const;
+ void addDisplayViewport(DisplayViewport viewport);
+ void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation,
+ bool isActive, const std::string& uniqueId,
+ std::optional<uint8_t> physicalPort, ViewportType type);
+ bool updateViewport(const DisplayViewport& viewport);
+ void addExcludedDeviceName(const std::string& deviceName);
+ void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort);
+ void addInputUniqueIdAssociation(const std::string& inputUniqueId,
+ const std::string& displayUniqueId);
+ void addDisabledDevice(int32_t deviceId);
+ void removeDisabledDevice(int32_t deviceId);
+ void setPointerController(std::shared_ptr<FakePointerController> controller);
+ const InputReaderConfiguration* getReaderConfiguration() const;
+ const std::vector<InputDeviceInfo>& getInputDevices() const;
+ TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
+ int32_t surfaceRotation);
+ void setTouchAffineTransformation(const TouchAffineTransformation t);
+ PointerCaptureRequest setPointerCapture(bool enabled);
+ void setShowTouches(bool enabled);
+ void setDefaultPointerDisplayId(int32_t pointerDisplayId);
+ void setPointerGestureEnabled(bool enabled);
+ float getPointerGestureMovementSpeedRatio();
+ float getPointerGestureZoomSpeedRatio();
+ void setVelocityControlParams(const VelocityControlParameters& params);
+
+private:
+ void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
+ std::shared_ptr<PointerControllerInterface> obtainPointerController(
+ int32_t /*deviceId*/) override;
+ void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+ std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
+ const InputDeviceIdentifier&) override;
+ std::string getDeviceAlias(const InputDeviceIdentifier&) override;
+ void waitForInputDevices(std::function<void(bool)> processDevicesChanged);
+ void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
+
+ std::mutex mLock;
+ std::condition_variable mDevicesChangedCondition;
+
+ InputReaderConfiguration mConfig;
+ std::shared_ptr<FakePointerController> mPointerController;
+ std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
+ bool mInputDevicesChanged GUARDED_BY(mLock){false};
+ std::vector<DisplayViewport> mViewports;
+ TouchAffineTransformation transform;
+ std::optional<int32_t /*deviceId*/> mStylusGestureNotified GUARDED_BY(mLock){};
+
+ uint32_t mNextPointerCaptureSequenceNumber{0};
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
new file mode 100644
index 0000000..635366b
--- /dev/null
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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 "FakePointerController.h"
+
+namespace android {
+
+void FakePointerController::setBounds(float minX, float minY, float maxX, float maxY) {
+ mHaveBounds = true;
+ mMinX = minX;
+ mMinY = minY;
+ mMaxX = maxX;
+ mMaxY = maxY;
+}
+
+const std::map<int32_t, std::vector<int32_t>>& FakePointerController::getSpots() {
+ return mSpotsByDisplay;
+}
+
+void FakePointerController::setPosition(float x, float y) {
+ mX = x;
+ mY = y;
+}
+
+void FakePointerController::setButtonState(int32_t buttonState) {
+ mButtonState = buttonState;
+}
+
+int32_t FakePointerController::getButtonState() const {
+ return mButtonState;
+}
+
+void FakePointerController::getPosition(float* outX, float* outY) const {
+ *outX = mX;
+ *outY = mY;
+}
+
+int32_t FakePointerController::getDisplayId() const {
+ return mDisplayId;
+}
+
+void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) {
+ mDisplayId = viewport.displayId;
+}
+
+bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
+ float* outMaxY) const {
+ *outMinX = mMinX;
+ *outMinY = mMinY;
+ *outMaxX = mMaxX;
+ *outMaxY = mMaxY;
+ return mHaveBounds;
+}
+
+void FakePointerController::move(float deltaX, float deltaY) {
+ mX += deltaX;
+ if (mX < mMinX) mX = mMinX;
+ if (mX > mMaxX) mX = mMaxX;
+ mY += deltaY;
+ if (mY < mMinY) mY = mMinY;
+ if (mY > mMaxY) mY = mMaxY;
+}
+
+void FakePointerController::setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
+ int32_t displayId) {
+ std::vector<int32_t> newSpots;
+ // Add spots for fingers that are down.
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ newSpots.push_back(id);
+ }
+
+ mSpotsByDisplay[displayId] = newSpots;
+}
+
+void FakePointerController::clearSpots() {
+ mSpotsByDisplay.clear();
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
new file mode 100644
index 0000000..f00870f
--- /dev/null
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <PointerControllerInterface.h>
+#include <gui/constants.h>
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <utils/BitSet.h>
+
+namespace android {
+
+class FakePointerController : public PointerControllerInterface {
+public:
+ virtual ~FakePointerController() {}
+
+ void setBounds(float minX, float minY, float maxX, float maxY);
+ const std::map<int32_t, std::vector<int32_t>>& getSpots();
+
+ void setPosition(float x, float y) override;
+ void setButtonState(int32_t buttonState) override;
+ int32_t getButtonState() const override;
+ void getPosition(float* outX, float* outY) const override;
+ int32_t getDisplayId() const override;
+ void setDisplayViewport(const DisplayViewport& viewport) override;
+
+private:
+ bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override;
+ void move(float deltaX, float deltaY) override;
+ void fade(Transition) override {}
+ void unfade(Transition) override {}
+ void setPresentation(Presentation) override {}
+ void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
+ int32_t displayId) override;
+ void clearSpots() override;
+
+ bool mHaveBounds{false};
+ float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0};
+ float mX{0}, mY{0};
+ int32_t mButtonState{0};
+ int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+
+ std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 1fcc813..c72d01f 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -41,6 +41,8 @@
#include <thread>
#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "FakePointerController.h"
#include "TestConstants.h"
#include "android/hardware/input/InputDeviceCountryCode.h"
#include "input/DisplayViewport.h"
@@ -148,313 +150,6 @@
}
}
-// --- FakePointerController ---
-
-class FakePointerController : public PointerControllerInterface {
- bool mHaveBounds;
- float mMinX, mMinY, mMaxX, mMaxY;
- float mX, mY;
- int32_t mButtonState;
- int32_t mDisplayId;
-
-public:
- FakePointerController() :
- mHaveBounds(false), mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mX(0), mY(0),
- mButtonState(0), mDisplayId(ADISPLAY_ID_DEFAULT) {
- }
-
- virtual ~FakePointerController() {}
-
- void setBounds(float minX, float minY, float maxX, float maxY) {
- mHaveBounds = true;
- mMinX = minX;
- mMinY = minY;
- mMaxX = maxX;
- mMaxY = maxY;
- }
-
- void setPosition(float x, float y) override {
- mX = x;
- mY = y;
- }
-
- void setButtonState(int32_t buttonState) override { mButtonState = buttonState; }
-
- int32_t getButtonState() const override { return mButtonState; }
-
- void getPosition(float* outX, float* outY) const override {
- *outX = mX;
- *outY = mY;
- }
-
- int32_t getDisplayId() const override { return mDisplayId; }
-
- void setDisplayViewport(const DisplayViewport& viewport) override {
- mDisplayId = viewport.displayId;
- }
-
- const std::map<int32_t, std::vector<int32_t>>& getSpots() {
- return mSpotsByDisplay;
- }
-
-private:
- bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override {
- *outMinX = mMinX;
- *outMinY = mMinY;
- *outMaxX = mMaxX;
- *outMaxY = mMaxY;
- return mHaveBounds;
- }
-
- void move(float deltaX, float deltaY) override {
- mX += deltaX;
- if (mX < mMinX) mX = mMinX;
- if (mX > mMaxX) mX = mMaxX;
- mY += deltaY;
- if (mY < mMinY) mY = mMinY;
- if (mY > mMaxY) mY = mMaxY;
- }
-
- void fade(Transition) override {}
-
- void unfade(Transition) override {}
-
- void setPresentation(Presentation) override {}
-
- void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
- int32_t displayId) override {
- std::vector<int32_t> newSpots;
- // Add spots for fingers that are down.
- for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
- uint32_t id = idBits.clearFirstMarkedBit();
- newSpots.push_back(id);
- }
-
- mSpotsByDisplay[displayId] = newSpots;
- }
-
- void clearSpots() override { mSpotsByDisplay.clear(); }
-
- std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
-};
-
-
-// --- FakeInputReaderPolicy ---
-
-class FakeInputReaderPolicy : public InputReaderPolicyInterface {
- std::mutex mLock;
- std::condition_variable mDevicesChangedCondition;
-
- InputReaderConfiguration mConfig;
- std::shared_ptr<FakePointerController> mPointerController;
- std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
- bool mInputDevicesChanged GUARDED_BY(mLock){false};
- std::vector<DisplayViewport> mViewports;
- TouchAffineTransformation transform;
- std::optional<int32_t /*deviceId*/> mStylusGestureNotified GUARDED_BY(mLock){};
-
-protected:
- virtual ~FakeInputReaderPolicy() {}
-
-public:
- FakeInputReaderPolicy() {
- }
-
- void assertInputDevicesChanged() {
- waitForInputDevices([](bool devicesChanged) {
- if (!devicesChanged) {
- FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called.";
- }
- });
- }
-
- void assertInputDevicesNotChanged() {
- waitForInputDevices([](bool devicesChanged) {
- if (devicesChanged) {
- FAIL() << "Expected notifyInputDevicesChanged() to not be called.";
- }
- });
- }
-
- void assertStylusGestureNotified(int32_t deviceId) {
- std::scoped_lock lock(mLock);
- ASSERT_TRUE(mStylusGestureNotified);
- ASSERT_EQ(deviceId, *mStylusGestureNotified);
- mStylusGestureNotified.reset();
- }
-
- void assertStylusGestureNotNotified() {
- std::scoped_lock lock(mLock);
- ASSERT_FALSE(mStylusGestureNotified);
- }
-
- virtual void clearViewports() {
- mViewports.clear();
- mConfig.setDisplayViewports(mViewports);
- }
-
- std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const {
- return mConfig.getDisplayViewportByUniqueId(uniqueId);
- }
- std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const {
- return mConfig.getDisplayViewportByType(type);
- }
-
- std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const {
- return mConfig.getDisplayViewportByPort(displayPort);
- }
-
- void addDisplayViewport(DisplayViewport viewport) {
- mViewports.push_back(std::move(viewport));
- mConfig.setDisplayViewports(mViewports);
- }
-
- void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation,
- bool isActive, const std::string& uniqueId,
- std::optional<uint8_t> physicalPort, ViewportType type) {
- const bool isRotated =
- (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270);
- DisplayViewport v;
- v.displayId = displayId;
- v.orientation = orientation;
- v.logicalLeft = 0;
- v.logicalTop = 0;
- v.logicalRight = isRotated ? height : width;
- v.logicalBottom = isRotated ? width : height;
- v.physicalLeft = 0;
- v.physicalTop = 0;
- v.physicalRight = isRotated ? height : width;
- v.physicalBottom = isRotated ? width : height;
- v.deviceWidth = isRotated ? height : width;
- v.deviceHeight = isRotated ? width : height;
- v.isActive = isActive;
- v.uniqueId = uniqueId;
- v.physicalPort = physicalPort;
- v.type = type;
-
- addDisplayViewport(v);
- }
-
- bool updateViewport(const DisplayViewport& viewport) {
- size_t count = mViewports.size();
- for (size_t i = 0; i < count; i++) {
- const DisplayViewport& currentViewport = mViewports[i];
- if (currentViewport.displayId == viewport.displayId) {
- mViewports[i] = viewport;
- mConfig.setDisplayViewports(mViewports);
- return true;
- }
- }
- // no viewport found.
- return false;
- }
-
- void addExcludedDeviceName(const std::string& deviceName) {
- mConfig.excludedDeviceNames.push_back(deviceName);
- }
-
- void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort) {
- mConfig.portAssociations.insert({inputPort, displayPort});
- }
-
- void addInputUniqueIdAssociation(const std::string& inputUniqueId,
- const std::string& displayUniqueId) {
- mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
- }
-
- void addDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.insert(deviceId); }
-
- void removeDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.erase(deviceId); }
-
- void setPointerController(std::shared_ptr<FakePointerController> controller) {
- mPointerController = std::move(controller);
- }
-
- const InputReaderConfiguration* getReaderConfiguration() const {
- return &mConfig;
- }
-
- const std::vector<InputDeviceInfo>& getInputDevices() const {
- return mInputDevices;
- }
-
- TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
- int32_t surfaceRotation) {
- return transform;
- }
-
- void setTouchAffineTransformation(const TouchAffineTransformation t) {
- transform = t;
- }
-
- PointerCaptureRequest setPointerCapture(bool enabled) {
- mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++};
- return mConfig.pointerCaptureRequest;
- }
-
- void setShowTouches(bool enabled) {
- mConfig.showTouches = enabled;
- }
-
- void setDefaultPointerDisplayId(int32_t pointerDisplayId) {
- mConfig.defaultPointerDisplayId = pointerDisplayId;
- }
-
- void setPointerGestureEnabled(bool enabled) { mConfig.pointerGesturesEnabled = enabled; }
-
- float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; }
-
- float getPointerGestureZoomSpeedRatio() { return mConfig.pointerGestureZoomSpeedRatio; }
-
- void setVelocityControlParams(const VelocityControlParameters& params) {
- mConfig.pointerVelocityControlParameters = params;
- mConfig.wheelVelocityControlParameters = params;
- }
-
-private:
- uint32_t mNextPointerCaptureSequenceNumber = 0;
-
- void getReaderConfiguration(InputReaderConfiguration* outConfig) override {
- *outConfig = mConfig;
- }
-
- std::shared_ptr<PointerControllerInterface> obtainPointerController(
- int32_t /*deviceId*/) override {
- return mPointerController;
- }
-
- void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {
- std::scoped_lock<std::mutex> lock(mLock);
- mInputDevices = inputDevices;
- mInputDevicesChanged = true;
- mDevicesChangedCondition.notify_all();
- }
-
- std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
- const InputDeviceIdentifier&) override {
- return nullptr;
- }
-
- std::string getDeviceAlias(const InputDeviceIdentifier&) override { return ""; }
-
- void waitForInputDevices(std::function<void(bool)> processDevicesChanged) {
- std::unique_lock<std::mutex> lock(mLock);
- base::ScopedLockAssertion assumeLocked(mLock);
-
- const bool devicesChanged =
- mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
- return mInputDevicesChanged;
- });
- ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
- mInputDevicesChanged = false;
- }
-
- void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override {
- std::scoped_lock<std::mutex> lock(mLock);
- mStylusGestureNotified = deviceId;
- }
-};
-
// --- FakeInputMapper ---
class FakeInputMapper : public InputMapper {
diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp
index 46f00e8..398cdf9 100644
--- a/services/sensorservice/SensorInterface.cpp
+++ b/services/sensorservice/SensorInterface.cpp
@@ -87,6 +87,42 @@
// ---------------------------------------------------------------------------
+RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp<StateChangeCallback> callback)
+ : BaseSensor(sensor), mCallback(std::move(callback)) {
+}
+
+status_t RuntimeSensor::activate(void*, bool enabled) {
+ if (enabled != mEnabled) {
+ mEnabled = enabled;
+ mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+ }
+ return OK;
+}
+
+status_t RuntimeSensor::batch(void*, int, int, int64_t samplingPeriodNs,
+ int64_t maxBatchReportLatencyNs) {
+ if (mSamplingPeriodNs != samplingPeriodNs || mBatchReportLatencyNs != maxBatchReportLatencyNs) {
+ mSamplingPeriodNs = samplingPeriodNs;
+ mBatchReportLatencyNs = maxBatchReportLatencyNs;
+ if (mEnabled) {
+ mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+ }
+ }
+ return OK;
+}
+
+status_t RuntimeSensor::setDelay(void*, int, int64_t ns) {
+ if (mSamplingPeriodNs != ns) {
+ mSamplingPeriodNs = ns;
+ if (mEnabled) {
+ mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+ }
+ }
+ return OK;
+}
+
+// ---------------------------------------------------------------------------
+
ProximitySensor::ProximitySensor(const sensor_t& sensor, SensorService& service)
: HardwareSensor(sensor), mSensorService(service) {
}
diff --git a/services/sensorservice/SensorInterface.h b/services/sensorservice/SensorInterface.h
index 5704359..5ee5e12 100644
--- a/services/sensorservice/SensorInterface.h
+++ b/services/sensorservice/SensorInterface.h
@@ -104,6 +104,32 @@
// ---------------------------------------------------------------------------
+class RuntimeSensor : public BaseSensor {
+public:
+ static constexpr int DEFAULT_DEVICE_ID = 0;
+
+ class StateChangeCallback : public virtual RefBase {
+ public:
+ virtual void onStateChanged(bool enabled, int64_t samplingPeriodNs,
+ int64_t batchReportLatencyNs) = 0;
+ };
+ RuntimeSensor(const sensor_t& sensor, sp<StateChangeCallback> callback);
+ virtual status_t activate(void* ident, bool enabled) override;
+ virtual status_t batch(void* ident, int handle, int flags, int64_t samplingPeriodNs,
+ int64_t maxBatchReportLatencyNs) override;
+ virtual status_t setDelay(void* ident, int handle, int64_t ns) override;
+ virtual bool process(sensors_event_t*, const sensors_event_t&) { return false; }
+ virtual bool isVirtual() const override { return false; }
+
+private:
+ bool mEnabled = false;
+ int64_t mSamplingPeriodNs = 0;
+ int64_t mBatchReportLatencyNs = 0;
+ sp<StateChangeCallback> mCallback;
+};
+
+// ---------------------------------------------------------------------------
+
class ProximitySensor : public HardwareSensor {
public:
explicit ProximitySensor(const sensor_t& sensor, SensorService& service);
diff --git a/services/sensorservice/SensorList.cpp b/services/sensorservice/SensorList.cpp
index 85ce0f0..6d36b47 100644
--- a/services/sensorservice/SensorList.cpp
+++ b/services/sensorservice/SensorList.cpp
@@ -29,12 +29,12 @@
const Sensor SensorList::mNonSensor = Sensor("unknown");
bool SensorList::add(
- int handle, SensorInterface* si, bool isForDebug, bool isVirtual) {
+ int handle, SensorInterface* si, bool isForDebug, bool isVirtual, int deviceId) {
std::lock_guard<std::mutex> lk(mLock);
if (handle == si->getSensor().getHandle() &&
mUsedHandle.insert(handle).second) {
// will succeed as the mUsedHandle does not have this handle
- mHandleMap.emplace(handle, Entry(si, isForDebug, isVirtual));
+ mHandleMap.emplace(handle, Entry(si, isForDebug, isVirtual, deviceId));
return true;
}
// handle exist already or handle mismatch
@@ -79,7 +79,8 @@
Vector<Sensor> sensors;
forEachEntry(
[&sensors] (const Entry& e) -> bool {
- if (!e.isForDebug && !e.si->getSensor().isDynamicSensor()) {
+ if (!e.isForDebug && !e.si->getSensor().isDynamicSensor()
+ && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) {
sensors.add(e.si->getSensor());
}
return true;
@@ -92,7 +93,8 @@
Vector<Sensor> sensors;
forEachEntry(
[&sensors] (const Entry& e) -> bool {
- if (!e.si->getSensor().isDynamicSensor()) {
+ if (!e.si->getSensor().isDynamicSensor()
+ && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) {
sensors.add(e.si->getSensor());
}
return true;
@@ -105,7 +107,8 @@
Vector<Sensor> sensors;
forEachEntry(
[&sensors] (const Entry& e) -> bool {
- if (!e.isForDebug && e.si->getSensor().isDynamicSensor()) {
+ if (!e.isForDebug && e.si->getSensor().isDynamicSensor()
+ && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) {
sensors.add(e.si->getSensor());
}
return true;
@@ -118,7 +121,20 @@
Vector<Sensor> sensors;
forEachEntry(
[&sensors] (const Entry& e) -> bool {
- if (e.isVirtual) {
+ if (e.isVirtual && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) {
+ sensors.add(e.si->getSensor());
+ }
+ return true;
+ });
+ return sensors;
+}
+
+const Vector<Sensor> SensorList::getRuntimeSensors(int deviceId) const {
+ // lock in forEachEntry
+ Vector<Sensor> sensors;
+ forEachEntry(
+ [&sensors, deviceId] (const Entry& e) -> bool {
+ if (!e.isForDebug && e.deviceId == deviceId) {
sensors.add(e.si->getSensor());
}
return true;
diff --git a/services/sensorservice/SensorList.h b/services/sensorservice/SensorList.h
index 049ae7c..79f6701 100644
--- a/services/sensorservice/SensorList.h
+++ b/services/sensorservice/SensorList.h
@@ -40,14 +40,16 @@
sp<SensorInterface> si;
const bool isForDebug;
const bool isVirtual;
- Entry(SensorInterface* si_, bool debug_, bool virtual_) :
- si(si_), isForDebug(debug_), isVirtual(virtual_) {
+ const int deviceId;
+ Entry(SensorInterface* si_, bool debug_, bool virtual_, int deviceId_) :
+ si(si_), isForDebug(debug_), isVirtual(virtual_), deviceId(deviceId_) {
}
};
// After SensorInterface * is added into SensorList, it can be assumed that SensorList own the
// object it pointed to and the object should not be released elsewhere.
- bool add(int handle, SensorInterface* si, bool isForDebug = false, bool isVirtual = false);
+ bool add(int handle, SensorInterface* si, bool isForDebug = false, bool isVirtual = false,
+ int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID);
// After a handle is removed, the object that SensorInterface * pointing to may get deleted if
// no more sp<> of the same object exist.
@@ -60,6 +62,7 @@
const Vector<Sensor> getUserDebugSensors() const;
const Vector<Sensor> getDynamicSensors() const;
const Vector<Sensor> getVirtualSensors() const;
+ const Vector<Sensor> getRuntimeSensors(int deviceId) const;
String8 getName(int handle) const;
String8 getStringType(int handle) const;
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 21d6b6b..0c9fef5 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -102,6 +102,33 @@
static const String16 sLocationHardwarePermission("android.permission.LOCATION_HARDWARE");
static const String16 sManageSensorsPermission("android.permission.MANAGE_SENSORS");
+namespace {
+
+// TODO(b/259227294): Move the sensor ranges to the HAL.
+int32_t nextRuntimeSensorHandle() {
+ static constexpr int32_t kRuntimeHandleBase = 0x5F000000;
+ static constexpr int32_t kRuntimeHandleEnd = 0x5FFFFFFF;
+ static int32_t nextHandle = kRuntimeHandleBase;
+ if (nextHandle == kRuntimeHandleEnd) {
+ return -1;
+ }
+ return nextHandle++;
+}
+
+class RuntimeSensorCallbackProxy : public RuntimeSensor::StateChangeCallback {
+ public:
+ RuntimeSensorCallbackProxy(sp<SensorService::RuntimeSensorStateChangeCallback> callback)
+ : mCallback(std::move(callback)) {}
+ void onStateChanged(bool enabled, int64_t samplingPeriodNs,
+ int64_t batchReportLatencyNs) override {
+ mCallback->onStateChanged(enabled, samplingPeriodNs, batchReportLatencyNs);
+ }
+ private:
+ sp<SensorService::RuntimeSensorStateChangeCallback> mCallback;
+};
+
+} // namespace
+
static bool isAutomotive() {
sp<IServiceManager> serviceManager = defaultServiceManager();
if (serviceManager.get() == nullptr) {
@@ -137,6 +164,60 @@
mMicSensorPrivacyPolicy = new MicrophonePrivacyPolicy(this);
}
+int SensorService::registerRuntimeSensor(
+ const sensor_t& sensor, int deviceId, sp<RuntimeSensorStateChangeCallback> callback) {
+ int handle = 0;
+ while (handle == 0 || !mSensors.isNewHandle(handle)) {
+ handle = nextRuntimeSensorHandle();
+ if (handle < 0) {
+ // Ran out of the dedicated range for runtime sensors.
+ return handle;
+ }
+ }
+
+ ALOGI("Registering runtime sensor handle 0x%x, type %d, name %s",
+ handle, sensor.type, sensor.name);
+
+ sp<RuntimeSensor::StateChangeCallback> runtimeSensorCallback(
+ new RuntimeSensorCallbackProxy(std::move(callback)));
+ sensor_t runtimeSensor = sensor;
+ // force the handle to be consistent
+ runtimeSensor.handle = handle;
+ SensorInterface *si = new RuntimeSensor(runtimeSensor, std::move(runtimeSensorCallback));
+
+ Mutex::Autolock _l(mLock);
+ const Sensor& s = registerSensor(si, /* isDebug= */ false, /* isVirtual= */ false, deviceId);
+
+ if (s.getHandle() != handle) {
+ // The registration was unsuccessful.
+ return s.getHandle();
+ }
+ return handle;
+}
+
+status_t SensorService::unregisterRuntimeSensor(int handle) {
+ ALOGI("Unregistering runtime sensor handle 0x%x disconnected", handle);
+ {
+ Mutex::Autolock _l(mLock);
+ if (!unregisterDynamicSensorLocked(handle)) {
+ ALOGE("Runtime sensor release error.");
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
+ for (const sp<SensorEventConnection>& connection : connLock.getActiveConnections()) {
+ connection->removeSensor(handle);
+ }
+ return OK;
+}
+
+status_t SensorService::sendRuntimeSensorEvent(const sensors_event_t& event) {
+ Mutex::Autolock _l(mLock);
+ mRuntimeSensorEventQueue.push(event);
+ return OK;
+}
+
bool SensorService::initializeHmacKey() {
int fd = open(SENSOR_SERVICE_HMAC_KEY_FILE, O_RDONLY|O_CLOEXEC);
if (fd != -1) {
@@ -407,10 +488,11 @@
&& isUidActive(uid) && !isOperationRestrictedLocked(opPackageName);
}
-const Sensor& SensorService::registerSensor(SensorInterface* s, bool isDebug, bool isVirtual) {
+const Sensor& SensorService::registerSensor(SensorInterface* s, bool isDebug, bool isVirtual,
+ int deviceId) {
int handle = s->getSensor().getHandle();
int type = s->getSensor().getType();
- if (mSensors.add(handle, s, isDebug, isVirtual)){
+ if (mSensors.add(handle, s, isDebug, isVirtual, deviceId)) {
mRecentEvent.emplace(handle, new SensorServiceUtil::RecentEventLogger(type));
return s->getSensor();
} else {
@@ -1003,6 +1085,7 @@
recordLastValueLocked(mSensorEventBuffer, count);
// handle virtual sensors
+ bool bufferNeedsSorting = false;
if (count && vcount) {
sensors_event_t const * const event = mSensorEventBuffer;
if (!mActiveVirtualSensors.empty()) {
@@ -1038,12 +1121,37 @@
// record the last synthesized values
recordLastValueLocked(&mSensorEventBuffer[count], k);
count += k;
- // sort the buffer by time-stamps
- sortEventBuffer(mSensorEventBuffer, count);
+ bufferNeedsSorting = true;
}
}
}
+ // handle runtime sensors
+ {
+ size_t k = 0;
+ while (!mRuntimeSensorEventQueue.empty()) {
+ if (count + k >= minBufferSize) {
+ ALOGE("buffer too small to hold all events: count=%zd, k=%zu, size=%zu",
+ count, k, minBufferSize);
+ break;
+ }
+ mSensorEventBuffer[count + k] = mRuntimeSensorEventQueue.front();
+ mRuntimeSensorEventQueue.pop();
+ k++;
+ }
+ if (k) {
+ // record the last synthesized values
+ recordLastValueLocked(&mSensorEventBuffer[count], k);
+ count += k;
+ bufferNeedsSorting = true;
+ }
+ }
+
+ if (bufferNeedsSorting) {
+ // sort the buffer by time-stamps
+ sortEventBuffer(mSensorEventBuffer, count);
+ }
+
// handle backward compatibility for RotationVector sensor
if (halVersion < SENSORS_DEVICE_API_VERSION_1_0) {
for (int i = 0; i < count; i++) {
@@ -1342,19 +1450,37 @@
return accessibleSensorList;
}
+void SensorService::addSensorIfAccessible(const String16& opPackageName, const Sensor& sensor,
+ Vector<Sensor>& accessibleSensorList) {
+ if (canAccessSensor(sensor, "can't see", opPackageName)) {
+ accessibleSensorList.add(sensor);
+ } else if (sensor.getType() != SENSOR_TYPE_HEAD_TRACKER) {
+ ALOGI("Skipped sensor %s because it requires permission %s and app op %" PRId32,
+ sensor.getName().string(), sensor.getRequiredPermission().string(),
+ sensor.getRequiredAppOp());
+ }
+}
+
Vector<Sensor> SensorService::getDynamicSensorList(const String16& opPackageName) {
Vector<Sensor> accessibleSensorList;
mSensors.forEachSensor(
[this, &opPackageName, &accessibleSensorList] (const Sensor& sensor) -> bool {
if (sensor.isDynamicSensor()) {
- if (canAccessSensor(sensor, "can't see", opPackageName)) {
- accessibleSensorList.add(sensor);
- } else if (sensor.getType() != SENSOR_TYPE_HEAD_TRACKER) {
- ALOGI("Skipped sensor %s because it requires permission %s and app op %" PRId32,
- sensor.getName().string(),
- sensor.getRequiredPermission().string(),
- sensor.getRequiredAppOp());
- }
+ addSensorIfAccessible(opPackageName, sensor, accessibleSensorList);
+ }
+ return true;
+ });
+ makeUuidsIntoIdsForSensorList(accessibleSensorList);
+ return accessibleSensorList;
+}
+
+Vector<Sensor> SensorService::getRuntimeSensorList(const String16& opPackageName, int deviceId) {
+ Vector<Sensor> accessibleSensorList;
+ mSensors.forEachEntry(
+ [this, &opPackageName, deviceId, &accessibleSensorList] (
+ const SensorServiceUtil::SensorList::Entry& e) -> bool {
+ if (e.deviceId == deviceId) {
+ addSensorIfAccessible(opPackageName, e.si->getSensor(), accessibleSensorList);
}
return true;
});
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 4ba3c51..e490398 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -42,6 +42,7 @@
#include <stdint.h>
#include <sys/types.h>
+#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -143,6 +144,14 @@
virtual void onProximityActive(bool isActive) = 0;
};
+ class RuntimeSensorStateChangeCallback : public virtual RefBase {
+ public:
+ // Note that the callback is invoked from an async thread and can interact with the
+ // SensorService directly.
+ virtual void onStateChanged(bool enabled, int64_t samplingPeriodNanos,
+ int64_t batchReportLatencyNanos) = 0;
+ };
+
static char const* getServiceName() ANDROID_API { return "sensorservice"; }
SensorService() ANDROID_API;
@@ -169,6 +178,11 @@
status_t addProximityActiveListener(const sp<ProximityActiveListener>& callback) ANDROID_API;
status_t removeProximityActiveListener(const sp<ProximityActiveListener>& callback) ANDROID_API;
+ int registerRuntimeSensor(const sensor_t& sensor, int deviceId,
+ sp<RuntimeSensorStateChangeCallback> callback) ANDROID_API;
+ status_t unregisterRuntimeSensor(int handle) ANDROID_API;
+ status_t sendRuntimeSensorEvent(const sensors_event_t& event) ANDROID_API;
+
// Returns true if a sensor should be throttled according to our rate-throttling rules.
static bool isSensorInCappedSet(int sensorType);
@@ -346,6 +360,7 @@
// ISensorServer interface
virtual Vector<Sensor> getSensorList(const String16& opPackageName);
virtual Vector<Sensor> getDynamicSensorList(const String16& opPackageName);
+ virtual Vector<Sensor> getRuntimeSensorList(const String16& opPackageName, int deviceId);
virtual sp<ISensorEventConnection> createSensorEventConnection(
const String8& packageName,
int requestedMode, const String16& opPackageName, const String16& attributionTag);
@@ -364,8 +379,9 @@
bool isWakeUpSensor(int type) const;
void recordLastValueLocked(sensors_event_t const* buffer, size_t count);
static void sortEventBuffer(sensors_event_t* buffer, size_t count);
- const Sensor& registerSensor(SensorInterface* sensor,
- bool isDebug = false, bool isVirtual = false);
+ const Sensor& registerSensor(SensorInterface* sensor, bool isDebug = false,
+ bool isVirtual = false,
+ int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID);
const Sensor& registerVirtualSensor(SensorInterface* sensor, bool isDebug = false);
const Sensor& registerDynamicSensorLocked(SensorInterface* sensor, bool isDebug = false);
bool unregisterDynamicSensorLocked(int handle);
@@ -375,6 +391,8 @@
sensors_event_t const* buffer, const int count);
bool canAccessSensor(const Sensor& sensor, const char* operation,
const String16& opPackageName);
+ void addSensorIfAccessible(const String16& opPackageName, const Sensor& sensor,
+ Vector<Sensor>& accessibleSensorList);
static bool hasPermissionForSensor(const Sensor& sensor);
static int getTargetSdkVersion(const String16& opPackageName);
static void resetTargetSdkVersionCache(const String16& opPackageName);
@@ -492,6 +510,7 @@
wp<const SensorEventConnection> * mMapFlushEventsToConnections;
std::unordered_map<int, SensorServiceUtil::RecentEventLogger*> mRecentEvent;
Mode mCurrentOperatingMode;
+ std::queue<sensors_event_t> mRuntimeSensorEventQueue;
// true if the head tracker sensor type is currently restricted to system usage only
// (can only be unrestricted for testing, via shell cmd)