Merge "Fix USAGE_FRONT_BUFFER failure on Cuttlefish" into udc-dev
diff --git a/include/android/sensor.h b/include/android/sensor.h
index 085fc27..16c5dde 100644
--- a/include/android/sensor.h
+++ b/include/android/sensor.h
@@ -611,10 +611,14 @@
* sensors_event_t
*/
typedef struct ASensorEvent {
- int32_t version; /* sizeof(struct ASensorEvent) */
- int32_t sensor; /** The sensor that generates this event */
- int32_t type; /** Sensor type for the event, such as {@link ASENSOR_TYPE_ACCELEROMETER} */
- int32_t reserved0; /** do not use */
+ /* sizeof(struct ASensorEvent) */
+ int32_t version;
+ /** The sensor that generates this event */
+ int32_t sensor;
+ /** Sensor type for the event, such as {@link ASENSOR_TYPE_ACCELEROMETER} */
+ int32_t type;
+ /** do not use */
+ int32_t reserved0;
/**
* The time in nanoseconds at which the event happened, and its behavior
* is identical to <a href="/reference/android/hardware/SensorEvent#timestamp">
diff --git a/include/input/Input.h b/include/input/Input.h
index 1e810b4..fe0c775 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -30,7 +30,6 @@
#include <stdint.h>
#include <ui/Transform.h>
#include <utils/BitSet.h>
-#include <utils/RefBase.h>
#include <utils/Timers.h>
#include <array>
#include <limits>
diff --git a/libs/cputimeinstate/cputimeinstate.cpp b/libs/cputimeinstate/cputimeinstate.cpp
index 706704a..4a7bd36 100644
--- a/libs/cputimeinstate/cputimeinstate.cpp
+++ b/libs/cputimeinstate/cputimeinstate.cpp
@@ -55,6 +55,7 @@
static uint32_t gNCpus = 0;
static std::vector<std::vector<uint32_t>> gPolicyFreqs;
static std::vector<std::vector<uint32_t>> gPolicyCpus;
+static std::vector<uint32_t> gCpuIndexMap;
static std::set<uint32_t> gAllFreqs;
static unique_fd gTisTotalMapFd;
static unique_fd gTisMapFd;
@@ -108,7 +109,7 @@
free(dirlist[i]);
}
free(dirlist);
-
+ uint32_t max_cpu_number = 0;
for (const auto &policy : policyFileNames) {
std::vector<uint32_t> freqs;
for (const auto &name : {"available", "boost"}) {
@@ -127,8 +128,19 @@
std::string path = StringPrintf("%s/%s/%s", basepath, policy.c_str(), "related_cpus");
auto cpus = readNumbersFromFile(path);
if (!cpus) return false;
+ for (auto cpu : *cpus) {
+ if(cpu > max_cpu_number)
+ max_cpu_number = cpu;
+ }
gPolicyCpus.emplace_back(*cpus);
}
+ gCpuIndexMap = std::vector<uint32_t>(max_cpu_number+1, -1);
+ uint32_t cpuorder = 0;
+ for (const auto &cpuList : gPolicyCpus) {
+ for (auto cpu : cpuList) {
+ gCpuIndexMap[cpu] = cpuorder++;
+ }
+ }
gTisTotalMapFd =
unique_fd{bpf_obj_get(BPF_FS_PATH "map_timeInState_total_time_in_state_map")};
@@ -277,7 +289,7 @@
for (uint32_t policyIdx = 0; policyIdx < gNPolicies; ++policyIdx) {
if (freqIdx >= gPolicyFreqs[policyIdx].size()) continue;
for (const auto &cpu : gPolicyCpus[policyIdx]) {
- out[policyIdx][freqIdx] += vals[cpu];
+ out[policyIdx][freqIdx] += vals[gCpuIndexMap[cpu]];
}
}
}
@@ -316,7 +328,8 @@
auto end = nextOffset < gPolicyFreqs[j].size() ? begin + FREQS_PER_ENTRY : out[j].end();
for (const auto &cpu : gPolicyCpus[j]) {
- std::transform(begin, end, std::begin(vals[cpu].ar), begin, std::plus<uint64_t>());
+ std::transform(begin, end, std::begin(vals[gCpuIndexMap[cpu]].ar), begin,
+ std::plus<uint64_t>());
}
}
}
@@ -382,7 +395,8 @@
auto end = nextOffset < gPolicyFreqs[i].size() ? begin + FREQS_PER_ENTRY :
map[key.uid][i].end();
for (const auto &cpu : gPolicyCpus[i]) {
- std::transform(begin, end, std::begin(vals[cpu].ar), begin, std::plus<uint64_t>());
+ std::transform(begin, end, std::begin(vals[gCpuIndexMap[cpu]].ar), begin,
+ std::plus<uint64_t>());
}
}
prevKey = key;
@@ -437,8 +451,8 @@
: ret.policy[policy].end();
for (const auto &cpu : gPolicyCpus[policy]) {
- std::transform(policyBegin, policyEnd, std::begin(vals[cpu].policy), policyBegin,
- std::plus<uint64_t>());
+ std::transform(policyBegin, policyEnd, std::begin(vals[gCpuIndexMap[cpu]].policy),
+ policyBegin, std::plus<uint64_t>());
}
}
}
@@ -506,8 +520,8 @@
: ret[key.uid].policy[policy].end();
for (const auto &cpu : gPolicyCpus[policy]) {
- std::transform(policyBegin, policyEnd, std::begin(vals[cpu].policy), policyBegin,
- std::plus<uint64_t>());
+ std::transform(policyBegin, policyEnd, std::begin(vals[gCpuIndexMap[cpu]].policy),
+ policyBegin, std::plus<uint64_t>());
}
}
} while (prevKey = key, !getNextMapKey(gConcurrentMapFd, &prevKey, &key));
@@ -640,7 +654,7 @@
auto end = nextOffset < gPolicyFreqs[j].size() ? begin + FREQS_PER_ENTRY
: map[key.aggregation_key][j].end();
for (const auto &cpu : gPolicyCpus[j]) {
- std::transform(begin, end, std::begin(vals[cpu].ar), begin,
+ std::transform(begin, end, std::begin(vals[gCpuIndexMap[cpu]].ar), begin,
std::plus<uint64_t>());
}
}
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
index 88038f1..00b66ae 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegr.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegr.h
@@ -373,14 +373,41 @@
jr_uncompressed_ptr dest);
/*
- * This method will check the validity of the input images.
+ * This method will check the validity of the input arguments.
*
* @param uncompressed_p010_image uncompressed HDR image in P010 color format
* @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
- * @return NO_ERROR if the input images are valid, error code is not valid.
+ * @param hdr_tf transfer function of the HDR image
+ * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+ * represents the maximum available size of the desitination buffer, and it must be
+ * set before calling this method. If the encoded JPEGR size exceeds
+ * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+ * @return NO_ERROR if the input args are valid, error code is not valid.
*/
- status_t areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image);
+ status_t areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
+ jr_uncompressed_ptr uncompressed_yuv_420_image,
+ ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest);
+
+ /*
+ * This method will check the validity of the input arguments.
+ *
+ * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+ * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+ * @param hdr_tf transfer function of the HDR image
+ * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+ * represents the maximum available size of the desitination buffer, and it must be
+ * set before calling this method. If the encoded JPEGR size exceeds
+ * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+ * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
+ * the highest quality
+ * @return NO_ERROR if the input args are valid, error code is not valid.
+ */
+ status_t areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
+ jr_uncompressed_ptr uncompressed_yuv_420_image,
+ ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest,
+ int quality);
};
} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
index f970936..d6153e9 100644
--- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h
+++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
@@ -24,6 +24,7 @@
ULTRAHDR_COLORGAMUT_BT709,
ULTRAHDR_COLORGAMUT_P3,
ULTRAHDR_COLORGAMUT_BT2100,
+ ULTRAHDR_COLORGAMUT_MAX = ULTRAHDR_COLORGAMUT_BT2100,
} ultrahdr_color_gamut;
// Transfer functions for image data
@@ -33,14 +34,17 @@
ULTRAHDR_TF_HLG = 1,
ULTRAHDR_TF_PQ = 2,
ULTRAHDR_TF_SRGB = 3,
+ ULTRAHDR_TF_MAX = ULTRAHDR_TF_SRGB,
} ultrahdr_transfer_function;
// Target output formats for decoder
typedef enum {
+ ULTRAHDR_OUTPUT_UNSPECIFIED = -1,
ULTRAHDR_OUTPUT_SDR, // SDR in RGBA_8888 color format
ULTRAHDR_OUTPUT_HDR_LINEAR, // HDR in F16 color format (linear)
ULTRAHDR_OUTPUT_HDR_PQ, // HDR in RGBA_1010102 color format (PQ transfer function)
ULTRAHDR_OUTPUT_HDR_HLG, // HDR in RGBA_1010102 color format (HLG transfer function)
+ ULTRAHDR_OUTPUT_MAX = ULTRAHDR_OUTPUT_HDR_HLG,
} ultrahdr_output_format;
/*
diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
index 12217b7..fac90c5 100644
--- a/libs/ultrahdr/jpegdecoderhelper.cpp
+++ b/libs/ultrahdr/jpegdecoderhelper.cpp
@@ -26,6 +26,8 @@
namespace android::ultrahdr {
+#define ALIGNM(x, m) ((((x) + ((m) - 1)) / (m)) * (m))
+
const uint32_t kAPP0Marker = JPEG_APP0; // JFIF
const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
@@ -224,7 +226,14 @@
cinfo.out_color_space = JCS_EXT_RGBA;
} else {
if (cinfo.jpeg_color_space == JCS_YCbCr) {
- // 1 byte per pixel for Y, 0.5 byte per pixel for U+V
+ if (cinfo.comp_info[0].h_samp_factor != 2 ||
+ cinfo.comp_info[1].h_samp_factor != 1 ||
+ cinfo.comp_info[2].h_samp_factor != 1 ||
+ cinfo.comp_info[0].v_samp_factor != 2 ||
+ cinfo.comp_info[1].v_samp_factor != 1 ||
+ cinfo.comp_info[2].v_samp_factor != 1) {
+ return false;
+ }
mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
} else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
@@ -342,7 +351,6 @@
}
bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
-
JSAMPROW y[kCompressBatchSize];
JSAMPROW cb[kCompressBatchSize / 2];
JSAMPROW cr[kCompressBatchSize / 2];
@@ -356,6 +364,32 @@
std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
memset(empty.get(), 0, cinfo->image_width);
+ const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
+ bool is_width_aligned = (aligned_width == cinfo->image_width);
+ std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
+ uint8_t* y_plane_intrm = nullptr;
+ uint8_t* u_plane_intrm = nullptr;
+ uint8_t* v_plane_intrm = nullptr;
+ JSAMPROW y_intrm[kCompressBatchSize];
+ JSAMPROW cb_intrm[kCompressBatchSize / 2];
+ JSAMPROW cr_intrm[kCompressBatchSize / 2];
+ JSAMPARRAY planes_intrm[3] {y_intrm, cb_intrm, cr_intrm};
+ if (!is_width_aligned) {
+ size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
+ buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
+ y_plane_intrm = buffer_intrm.get();
+ u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize);
+ v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4;
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ y_intrm[i] = y_plane_intrm + i * aligned_width;
+ }
+ for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+ int offset_intrm = i * (aligned_width / 2);
+ cb_intrm[i] = u_plane_intrm + offset_intrm;
+ cr_intrm[i] = v_plane_intrm + offset_intrm;
+ }
+ }
+
while (cinfo->output_scanline < cinfo->image_height) {
for (int i = 0; i < kCompressBatchSize; ++i) {
size_t scanline = cinfo->output_scanline + i;
@@ -377,11 +411,21 @@
}
}
- int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize);
+ int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
+ kCompressBatchSize);
if (processed != kCompressBatchSize) {
ALOGE("Number of processed lines does not equal input lines.");
return false;
}
+ if (!is_width_aligned) {
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ memcpy(y[i], y_intrm[i], cinfo->image_width);
+ }
+ for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+ memcpy(cb[i], cb_intrm[i], cinfo->image_width / 2);
+ memcpy(cr[i], cr_intrm[i], cinfo->image_width / 2);
+ }
+ }
}
return true;
}
@@ -394,6 +438,21 @@
std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
memset(empty.get(), 0, cinfo->image_width);
+ int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
+ bool is_width_aligned = (aligned_width == cinfo->image_width);
+ std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
+ uint8_t* y_plane_intrm = nullptr;
+ JSAMPROW y_intrm[kCompressBatchSize];
+ JSAMPARRAY planes_intrm[1] {y_intrm};
+ if (!is_width_aligned) {
+ size_t mcu_row_size = aligned_width * kCompressBatchSize;
+ buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
+ y_plane_intrm = buffer_intrm.get();
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ y_intrm[i] = y_plane_intrm + i * aligned_width;
+ }
+ }
+
while (cinfo->output_scanline < cinfo->image_height) {
for (int i = 0; i < kCompressBatchSize; ++i) {
size_t scanline = cinfo->output_scanline + i;
@@ -404,11 +463,17 @@
}
}
- int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize);
+ int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
+ kCompressBatchSize);
if (processed != kCompressBatchSize / 2) {
ALOGE("Number of processed lines does not equal input lines.");
return false;
}
+ if (!is_width_aligned) {
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ memcpy(y[i], y_intrm[i], cinfo->image_width);
+ }
+ }
}
return true;
}
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
index ef927e3..5ebca39 100644
--- a/libs/ultrahdr/jpegr.cpp
+++ b/libs/ultrahdr/jpegr.cpp
@@ -65,6 +65,13 @@
// Map is quarter res / sixteenth size
static const size_t kMapDimensionScaleFactor = 4;
+
+// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to
+// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale
+// 1 sample is sufficient. We are using 2 here anyways
+static const int kMinWidth = 2 * kMapDimensionScaleFactor;
+static const int kMinHeight = 2 * kMapDimensionScaleFactor;
+
// JPEG block size.
// JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma,
// and 8 x 8 for chroma.
@@ -89,23 +96,69 @@
return cpuCoreCount;
}
-status_t JpegR::areInputImagesValid(jr_uncompressed_ptr uncompressed_p010_image,
- jr_uncompressed_ptr uncompressed_yuv_420_image) {
- if (uncompressed_p010_image == nullptr) {
+status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
+ jr_uncompressed_ptr uncompressed_yuv_420_image,
+ ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest) {
+ if (uncompressed_p010_image == nullptr || uncompressed_p010_image->data == nullptr) {
+ ALOGE("received nullptr for uncompressed p010 image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
+ if (uncompressed_p010_image->width % 2 != 0
+ || uncompressed_p010_image->height % 2 != 0) {
+ ALOGE("Image dimensions cannot be odd, image dimensions %dx%d",
+ uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (uncompressed_p010_image->width < kMinWidth
+ || uncompressed_p010_image->height < kMinHeight) {
+ ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d",
+ kMinWidth, kMinHeight, uncompressed_p010_image->width, uncompressed_p010_image->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (uncompressed_p010_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
+ || uncompressed_p010_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
+ ALOGE("Unrecognized p010 color gamut %d", uncompressed_p010_image->colorGamut);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
if (uncompressed_p010_image->luma_stride != 0
&& uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) {
- ALOGE("Image stride can not be smaller than width, stride=%d, width=%d",
+ ALOGE("Luma stride can not be smaller than width, stride=%d, width=%d",
uncompressed_p010_image->luma_stride, uncompressed_p010_image->width);
return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
+ if (uncompressed_p010_image->chroma_data != nullptr
+ && uncompressed_p010_image->chroma_stride < uncompressed_p010_image->width) {
+ ALOGE("Chroma stride can not be smaller than width, stride=%d, width=%d",
+ uncompressed_p010_image->chroma_stride,
+ uncompressed_p010_image->width);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (dest == nullptr || dest->data == nullptr) {
+ ALOGE("received nullptr for destination");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX) {
+ ALOGE("Invalid hdr transfer function %d", hdr_tf);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
if (uncompressed_yuv_420_image == nullptr) {
return NO_ERROR;
}
+ if (uncompressed_yuv_420_image->data == nullptr) {
+ ALOGE("received nullptr for uncompressed 420 image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
if (uncompressed_yuv_420_image->luma_stride != 0) {
ALOGE("Stride is not supported for YUV420 image");
return ERROR_JPEGR_UNSUPPORTED_FEATURE;
@@ -127,6 +180,30 @@
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
+ if (uncompressed_yuv_420_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
+ || uncompressed_yuv_420_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
+ ALOGE("Unrecognized 420 color gamut %d", uncompressed_yuv_420_image->colorGamut);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ return NO_ERROR;
+}
+
+status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
+ jr_uncompressed_ptr uncompressed_yuv_420_image,
+ ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest,
+ int quality) {
+ if (status_t ret = areInputArgumentsValid(
+ uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
+ return ret;
+ }
+
+ if (quality < 0 || quality > 100) {
+ ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
return NO_ERROR;
}
@@ -136,19 +213,17 @@
jr_compressed_ptr dest,
int quality,
jr_exif_ptr exif) {
- if (uncompressed_p010_image == nullptr || dest == nullptr) {
- return ERROR_JPEGR_INVALID_NULL_PTR;
- }
-
- if (quality < 0 || quality > 100) {
- return ERROR_JPEGR_INVALID_INPUT_TYPE;
- }
-
- if (status_t ret = areInputImagesValid(
- uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr) != NO_ERROR) {
+ if (status_t ret = areInputArgumentsValid(
+ uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
+ hdr_tf, dest, quality) != NO_ERROR) {
return ret;
}
+ if (exif != nullptr && exif->data == nullptr) {
+ ALOGE("received nullptr for exif metadata");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
ultrahdr_metadata_struct metadata;
metadata.version = kJpegrVersion;
@@ -201,18 +276,19 @@
jr_compressed_ptr dest,
int quality,
jr_exif_ptr exif) {
- if (uncompressed_p010_image == nullptr
- || uncompressed_yuv_420_image == nullptr
- || dest == nullptr) {
+ if (uncompressed_yuv_420_image == nullptr) {
+ ALOGE("received nullptr for uncompressed 420 image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- if (quality < 0 || quality > 100) {
- return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ if (exif != nullptr && exif->data == nullptr) {
+ ALOGE("received nullptr for exif metadata");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
}
- if (status_t ret = areInputImagesValid(
- uncompressed_p010_image, uncompressed_yuv_420_image) != NO_ERROR) {
+ if (status_t ret = areInputArgumentsValid(
+ uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf,
+ dest, quality) != NO_ERROR) {
return ret;
}
@@ -256,15 +332,18 @@
jr_compressed_ptr compressed_jpeg_image,
ultrahdr_transfer_function hdr_tf,
jr_compressed_ptr dest) {
- if (uncompressed_p010_image == nullptr
- || uncompressed_yuv_420_image == nullptr
- || compressed_jpeg_image == nullptr
- || dest == nullptr) {
+ if (uncompressed_yuv_420_image == nullptr) {
+ ALOGE("received nullptr for uncompressed 420 image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- if (status_t ret = areInputImagesValid(
- uncompressed_p010_image, uncompressed_yuv_420_image) != NO_ERROR) {
+ if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
+ ALOGE("received nullptr for compressed jpeg image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ if (status_t ret = areInputArgumentsValid(
+ uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
return ret;
}
@@ -293,14 +372,14 @@
jr_compressed_ptr compressed_jpeg_image,
ultrahdr_transfer_function hdr_tf,
jr_compressed_ptr dest) {
- if (uncompressed_p010_image == nullptr
- || compressed_jpeg_image == nullptr
- || dest == nullptr) {
+ if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
+ ALOGE("received nullptr for compressed jpeg image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- if (status_t ret = areInputImagesValid(
- uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr) != NO_ERROR) {
+ if (status_t ret = areInputArgumentsValid(
+ uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
+ hdr_tf, dest) != NO_ERROR) {
return ret;
}
@@ -344,13 +423,34 @@
jr_compressed_ptr compressed_gainmap,
ultrahdr_metadata_ptr metadata,
jr_compressed_ptr dest) {
+ if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
+ ALOGE("received nullptr for compressed jpeg image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ if (compressed_gainmap == nullptr || compressed_gainmap->data == nullptr) {
+ ALOGE("received nullptr for compressed gain map");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ if (dest == nullptr || dest->data == nullptr) {
+ ALOGE("received nullptr for destination");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
metadata, dest));
return NO_ERROR;
}
status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) {
- if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
+ if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
+ ALOGE("received nullptr for compressed jpegr image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ if (jpegr_info == nullptr) {
+ ALOGE("received nullptr for compressed jpegr info struct");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
@@ -376,12 +476,34 @@
ultrahdr_output_format output_format,
jr_uncompressed_ptr gain_map,
ultrahdr_metadata_ptr metadata) {
- if (compressed_jpegr_image == nullptr || dest == nullptr) {
+ if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
+ ALOGE("received nullptr for compressed jpegr image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ if (dest == nullptr || dest->data == nullptr) {
+ ALOGE("received nullptr for dest image");
return ERROR_JPEGR_INVALID_NULL_PTR;
}
if (max_display_boost < 1.0f) {
- return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ ALOGE("received bad value for max_display_boost %f", max_display_boost);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (exif != nullptr && exif->data == nullptr) {
+ ALOGE("received nullptr address for exif data");
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
+ ALOGE("received bad value for output format %d", output_format);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (gain_map != nullptr && gain_map->data == nullptr) {
+ ALOGE("received nullptr address for gain map data");
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
}
if (output_format == ULTRAHDR_OUTPUT_SDR) {
@@ -1041,21 +1163,18 @@
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- size_t src_luma_stride = src->luma_stride;
- size_t src_chroma_stride = src->chroma_stride;
uint16_t* src_luma_data = reinterpret_cast<uint16_t*>(src->data);
- uint16_t* src_chroma_data = reinterpret_cast<uint16_t*>(src->chroma_data);
+ size_t src_luma_stride = src->luma_stride == 0 ? src->width : src->luma_stride;
- if (src_chroma_data == nullptr) {
- src_chroma_data = &reinterpret_cast<uint16_t*>(src->data)[src_luma_stride * src->height];
+ uint16_t* src_chroma_data;
+ size_t src_chroma_stride;
+ if (src->chroma_data == nullptr) {
+ src_chroma_stride = src_luma_stride;
+ src_chroma_data = &reinterpret_cast<uint16_t*>(src->data)[src_luma_stride * src->height];
+ } else {
+ src_chroma_stride = src->chroma_stride;
+ src_chroma_data = reinterpret_cast<uint16_t*>(src->chroma_data);
}
- if (src_luma_stride == 0) {
- src_luma_stride = src->width;
- }
- if (src_chroma_stride == 0) {
- src_chroma_stride = src_luma_stride;
- }
-
dest->width = src->width;
dest->height = src->height;
diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp
index 58cd8f4..d482ea1 100644
--- a/libs/ultrahdr/tests/jpegr_test.cpp
+++ b/libs/ultrahdr/tests/jpegr_test.cpp
@@ -89,6 +89,51 @@
return true;
}
+static bool loadP010Image(const char *filename, jr_uncompressed_ptr img,
+ bool isUVContiguous) {
+ int fd = open(filename, O_CLOEXEC);
+ if (fd < 0) {
+ return false;
+ }
+ const int bpp = 2;
+ int lumaStride = img->luma_stride == 0 ? img->width : img->luma_stride;
+ int lumaSize = bpp * lumaStride * img->height;
+ int chromaSize = bpp * (img->height / 2) *
+ (isUVContiguous ? lumaStride : img->chroma_stride);
+ img->data = malloc(lumaSize + (isUVContiguous ? chromaSize : 0));
+ if (img->data == nullptr) {
+ ALOGE("loadP010Image(): failed to allocate memory for luma data.");
+ return false;
+ }
+ uint8_t *mem = static_cast<uint8_t *>(img->data);
+ for (int i = 0; i < img->height; i++) {
+ if (read(fd, mem, img->width * bpp) != img->width * bpp) {
+ close(fd);
+ return false;
+ }
+ mem += lumaStride * bpp;
+ }
+ int chromaStride = lumaStride;
+ if (!isUVContiguous) {
+ img->chroma_data = malloc(chromaSize);
+ if (img->chroma_data == nullptr) {
+ ALOGE("loadP010Image(): failed to allocate memory for chroma data.");
+ return false;
+ }
+ mem = static_cast<uint8_t *>(img->chroma_data);
+ chromaStride = img->chroma_stride;
+ }
+ for (int i = 0; i < img->height / 2; i++) {
+ if (read(fd, mem, img->width * bpp) != img->width * bpp) {
+ close(fd);
+ return false;
+ }
+ mem += chromaStride * bpp;
+ }
+ close(fd);
+ return true;
+}
+
class JpegRTest : public testing::Test {
public:
JpegRTest();
@@ -98,10 +143,11 @@
virtual void SetUp();
virtual void TearDown();
- struct jpegr_uncompressed_struct mRawP010Image;
- struct jpegr_uncompressed_struct mRawP010ImageWithStride;
- struct jpegr_uncompressed_struct mRawYuv420Image;
- struct jpegr_compressed_struct mJpegImage;
+ struct jpegr_uncompressed_struct mRawP010Image{};
+ struct jpegr_uncompressed_struct mRawP010ImageWithStride{};
+ struct jpegr_uncompressed_struct mRawP010ImageWithChromaData{};
+ struct jpegr_uncompressed_struct mRawYuv420Image{};
+ struct jpegr_compressed_struct mJpegImage{};
};
JpegRTest::JpegRTest() {}
@@ -110,7 +156,11 @@
void JpegRTest::SetUp() {}
void JpegRTest::TearDown() {
free(mRawP010Image.data);
+ free(mRawP010Image.chroma_data);
free(mRawP010ImageWithStride.data);
+ free(mRawP010ImageWithStride.chroma_data);
+ free(mRawP010ImageWithChromaData.data);
+ free(mRawP010ImageWithChromaData.chroma_data);
free(mRawYuv420Image.data);
free(mJpegImage.data);
}
@@ -178,6 +228,639 @@
jpegRCodec.decodeJPEGR(nullptr, nullptr);
}
+/* Test Encode API-0 invalid arguments */
+TEST_F(JpegRTest, encodeAPI0ForInvalidArgs) {
+ int ret;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = 16 * sizeof(uint8_t);
+ jpegR.data = malloc(jpegR.maxLength);
+
+ JpegR jpegRCodec;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ mRawP010ImageWithStride.data = malloc(16);
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+
+ // test quality factor
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ -1, nullptr)) << "fail, API allows bad jpeg quality factor";
+
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ 101, nullptr)) << "fail, API allows bad jpeg quality factor";
+
+ // test hdr transfer function
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride,
+ static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride,
+ static_cast<ultrahdr_transfer_function>(-10),
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+
+ // test dest
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr dest";
+
+ // test p010 input
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr p010 image";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
+ ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
+
+ mRawP010ImageWithStride.width = 0;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = 0;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+ mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
+ mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride";
+
+ mRawP010ImageWithStride.chroma_data = nullptr;
+
+ free(jpegR.data);
+}
+
+/* Test Encode API-1 invalid arguments */
+TEST_F(JpegRTest, encodeAPI1ForInvalidArgs) {
+ int ret;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = 16 * sizeof(uint8_t);
+ jpegR.data = malloc(jpegR.maxLength);
+
+ JpegR jpegRCodec;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ mRawP010ImageWithStride.data = malloc(16);
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ mRawYuv420Image.data = malloc(16);
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+
+ // test quality factor
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, -1, nullptr)) << "fail, API allows bad jpeg quality factor";
+
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, 101, nullptr)) << "fail, API allows bad jpeg quality factor";
+
+ // test hdr transfer function
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image,
+ ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, &jpegR, DEFAULT_JPEG_QUALITY,
+ nullptr)) << "fail, API allows bad hdr transfer function";
+
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image,
+ static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image,
+ static_cast<ultrahdr_transfer_function>(-10),
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad hdr transfer function";
+
+ // test dest
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ nullptr, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr dest";
+
+ // test p010 input
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ nullptr, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr p010 image";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
+ ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad p010 color gamut";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
+
+ mRawP010ImageWithStride.width = 0;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image width";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = 0;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad image height";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+ mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
+ mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad chroma stride";
+
+ // test 420 input
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+ mRawP010ImageWithStride.chroma_data = nullptr;
+ mRawP010ImageWithStride.chroma_stride = 0;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR,
+ DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows nullptr for 420 image";
+
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT - 2;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 image width";
+
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH - 2;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 image height";
+
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ mRawYuv420Image.luma_stride = TEST_IMAGE_STRIDE;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad luma stride for 420";
+
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ mRawYuv420Image.luma_stride = 0;
+ mRawYuv420Image.chroma_data = mRawYuv420Image.data;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows chroma pointer for 420";
+
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ mRawYuv420Image.luma_stride = 0;
+ mRawYuv420Image.chroma_data = nullptr;
+ mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 color gamut";
+
+ mRawYuv420Image.colorGamut = static_cast<ultrahdr_color_gamut>(
+ ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR, DEFAULT_JPEG_QUALITY, nullptr)) << "fail, API allows bad 420 color gamut";
+
+ free(jpegR.data);
+}
+
+/* Test Encode API-2 invalid arguments */
+TEST_F(JpegRTest, encodeAPI2ForInvalidArgs) {
+ int ret;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = 16 * sizeof(uint8_t);
+ jpegR.data = malloc(jpegR.maxLength);
+
+ JpegR jpegRCodec;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ mRawP010ImageWithStride.data = malloc(16);
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ mRawYuv420Image.data = malloc(16);
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+
+ // test hdr transfer function
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+ &jpegR)) << "fail, API allows bad hdr transfer function";
+
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ &jpegR)) << "fail, API allows bad hdr transfer function";
+
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ static_cast<ultrahdr_transfer_function>(-10),
+ &jpegR)) << "fail, API allows bad hdr transfer function";
+
+ // test dest
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr)) << "fail, API allows nullptr dest";
+
+ // test p010 input
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ nullptr, &mRawYuv420Image, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows nullptr p010 image";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad p010 color gamut";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
+ ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad p010 color gamut";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image width";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image height";
+
+ mRawP010ImageWithStride.width = 0;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image width";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = 0;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad image height";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR)) << "fail, API allows bad luma stride";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+ mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
+ mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad chroma stride";
+
+ // test 420 input
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+ mRawP010ImageWithStride.chroma_data = nullptr;
+ mRawP010ImageWithStride.chroma_stride = 0;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, nullptr, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows nullptr for 420 image";
+
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT - 2;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad 420 image width";
+
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH - 2;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad 420 image height";
+
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ mRawYuv420Image.luma_stride = TEST_IMAGE_STRIDE;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad luma stride for 420";
+
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ mRawYuv420Image.luma_stride = 0;
+ mRawYuv420Image.chroma_data = mRawYuv420Image.data;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows chroma pointer for 420";
+
+ mRawYuv420Image.width = TEST_IMAGE_WIDTH;
+ mRawYuv420Image.height = TEST_IMAGE_HEIGHT;
+ mRawYuv420Image.luma_stride = 0;
+ mRawYuv420Image.chroma_data = nullptr;
+ mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad 420 color gamut";
+
+ mRawYuv420Image.colorGamut = static_cast<ultrahdr_color_gamut>(
+ ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, &jpegR,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad 420 color gamut";
+
+ // bad compressed image
+ mRawYuv420Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &mRawYuv420Image, nullptr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad 420 color gamut";
+
+ free(jpegR.data);
+}
+
+/* Test Encode API-3 invalid arguments */
+TEST_F(JpegRTest, encodeAPI3ForInvalidArgs) {
+ int ret;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = 16 * sizeof(uint8_t);
+ jpegR.data = malloc(jpegR.maxLength);
+
+ JpegR jpegRCodec;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ mRawP010ImageWithStride.data = malloc(16);
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+
+ // test hdr transfer function
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+ &jpegR)) << "fail, API allows bad hdr transfer function";
+
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR,
+ static_cast<ultrahdr_transfer_function>(ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ &jpegR)) << "fail, API allows bad hdr transfer function";
+
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, static_cast<ultrahdr_transfer_function>(-10),
+ &jpegR)) << "fail, API allows bad hdr transfer function";
+
+ // test dest
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ nullptr)) << "fail, API allows nullptr dest";
+
+ // test p010 input
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ nullptr, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows nullptr p010 image";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad p010 color gamut";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = static_cast<ultrahdr_color_gamut>(
+ ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad p010 color gamut";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH - 1;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad image width";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT - 1;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad image height";
+
+ mRawP010ImageWithStride.width = 0;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad image width";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = 0;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad image height";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH - 2;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad luma stride";
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_STRIDE;
+ mRawP010ImageWithStride.chroma_data = mRawP010ImageWithStride.data;
+ mRawP010ImageWithStride.chroma_stride = TEST_IMAGE_WIDTH - 2;
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, &jpegR, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad chroma stride";
+ mRawP010ImageWithStride.chroma_data = nullptr;
+
+ // bad compressed image
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegR)) << "fail, API allows bad 420 color gamut";
+
+ free(jpegR.data);
+}
+
+/* Test Encode API-4 invalid arguments */
+TEST_F(JpegRTest, encodeAPI4ForInvalidArgs) {
+ int ret;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = 16 * sizeof(uint8_t);
+ jpegR.data = malloc(jpegR.maxLength);
+
+ JpegR jpegRCodec;
+
+ // test dest
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &jpegR, &jpegR, nullptr, nullptr)) << "fail, API allows nullptr dest";
+
+ // test primary image
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ nullptr, &jpegR, nullptr, &jpegR)) << "fail, API allows nullptr primary image";
+
+ // test gain map
+ EXPECT_NE(OK, jpegRCodec.encodeJPEGR(
+ &jpegR, nullptr, nullptr, &jpegR)) << "fail, API allows nullptr gainmap image";
+
+ free(jpegR.data);
+}
+
+/* Test Decode API invalid arguments */
+TEST_F(JpegRTest, decodeAPIForInvalidArgs) {
+ int ret;
+
+ // we are not really compressing anything so lets keep allocs to a minimum
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = 16 * sizeof(uint8_t);
+ jpegR.data = malloc(jpegR.maxLength);
+
+ // we are not really decoding anything so lets keep allocs to a minimum
+ mRawP010Image.data = malloc(16);
+
+ JpegR jpegRCodec;
+
+ // test jpegr image
+ EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
+ nullptr, &mRawP010Image)) << "fail, API allows nullptr for jpegr img";
+
+ // test dest image
+ EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
+ &jpegR, nullptr)) << "fail, API allows nullptr for dest";
+
+ // test max display boost
+ EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
+ &jpegR, &mRawP010Image, 0.5)) << "fail, API allows invalid max display boost";
+
+ // test output format
+ EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
+ &jpegR, &mRawP010Image, 0.5, nullptr,
+ static_cast<ultrahdr_output_format>(-1))) << "fail, API allows invalid output format";
+
+ EXPECT_NE(OK, jpegRCodec.decodeJPEGR(
+ &jpegR, &mRawP010Image, 0.5, nullptr,
+ static_cast<ultrahdr_output_format>(ULTRAHDR_OUTPUT_MAX + 1)))
+ << "fail, API allows invalid output format";
+
+ free(jpegR.data);
+}
+
TEST_F(JpegRTest, writeXmpThenRead) {
ultrahdr_metadata_struct metadata_expected;
metadata_expected.version = "1.0";
@@ -201,6 +884,81 @@
EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost);
}
+/* Test Encode API-0 */
+TEST_F(JpegRTest, encodeFromP010) {
+ int ret;
+
+ mRawP010Image.width = TEST_IMAGE_WIDTH;
+ mRawP010Image.height = TEST_IMAGE_HEIGHT;
+ mRawP010Image.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ // Load input files.
+ if (!loadP010Image(RAW_P010_IMAGE, &mRawP010Image, true)) {
+ FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+ }
+
+ JpegR jpegRCodec;
+
+ jpegr_compressed_struct jpegR;
+ jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
+ jpegR.data = malloc(jpegR.maxLength);
+ ret = jpegRCodec.encodeJPEGR(
+ &mRawP010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY,
+ nullptr);
+ if (ret != OK) {
+ FAIL() << "Error code is " << ret;
+ }
+
+ mRawP010ImageWithStride.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithStride.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithStride.luma_stride = TEST_IMAGE_WIDTH + 128;
+ mRawP010ImageWithStride.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ // Load input files.
+ if (!loadP010Image(RAW_P010_IMAGE, &mRawP010ImageWithStride, true)) {
+ FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+ }
+
+ jpegr_compressed_struct jpegRWithStride;
+ jpegRWithStride.maxLength = jpegR.length;
+ jpegRWithStride.data = malloc(jpegRWithStride.maxLength);
+ ret = jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithStride, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, &jpegRWithStride,
+ DEFAULT_JPEG_QUALITY, nullptr);
+ if (ret != OK) {
+ FAIL() << "Error code is " << ret;
+ }
+ ASSERT_EQ(jpegR.length, jpegRWithStride.length)
+ << "Same input is yielding different output";
+ ASSERT_EQ(0, memcmp(jpegR.data, jpegRWithStride.data, jpegR.length))
+ << "Same input is yielding different output";
+
+ mRawP010ImageWithChromaData.width = TEST_IMAGE_WIDTH;
+ mRawP010ImageWithChromaData.height = TEST_IMAGE_HEIGHT;
+ mRawP010ImageWithChromaData.luma_stride = TEST_IMAGE_WIDTH + 64;
+ mRawP010ImageWithChromaData.chroma_stride = TEST_IMAGE_WIDTH + 256;
+ mRawP010ImageWithChromaData.colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ // Load input files.
+ if (!loadP010Image(RAW_P010_IMAGE, &mRawP010ImageWithChromaData, false)) {
+ FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+ }
+ jpegr_compressed_struct jpegRWithChromaData;
+ jpegRWithChromaData.maxLength = jpegR.length;
+ jpegRWithChromaData.data = malloc(jpegRWithChromaData.maxLength);
+ ret = jpegRCodec.encodeJPEGR(
+ &mRawP010ImageWithChromaData, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ &jpegRWithChromaData, DEFAULT_JPEG_QUALITY, nullptr);
+ if (ret != OK) {
+ FAIL() << "Error code is " << ret;
+ }
+ ASSERT_EQ(jpegR.length, jpegRWithChromaData.length)
+ << "Same input is yielding different output";
+ ASSERT_EQ(0, memcmp(jpegR.data, jpegRWithChromaData.data, jpegR.length))
+ << "Same input is yielding different output";
+
+ free(jpegR.data);
+ free(jpegRWithStride.data);
+ free(jpegRWithChromaData.data);
+}
+
/* Test Encode API-0 and decode */
TEST_F(JpegRTest, encodeFromP010ThenDecode) {
int ret;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index a3c129e..0cc7cfb 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2561,9 +2561,17 @@
std::vector<TouchedWindow> hoveringWindows =
getHoveringWindowsLocked(oldState, tempTouchState, entry);
for (const TouchedWindow& touchedWindow : hoveringWindows) {
- addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
- touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
- targets);
+ std::optional<InputTarget> target =
+ createInputTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+ touchedWindow.firstDownTimeInTarget);
+ if (!target) {
+ continue;
+ }
+ // Hardcode to single hovering pointer for now.
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ pointerIds.set(entry.pointerProperties[0].id);
+ target->addPointers(pointerIds, touchedWindow.windowHandle->getInfo()->transform);
+ targets.push_back(*target);
}
}
@@ -2811,6 +2819,30 @@
}
}
+std::optional<InputTarget> InputDispatcher::createInputTargetLocked(
+ const sp<android::gui::WindowInfoHandle>& windowHandle,
+ ftl::Flags<InputTarget::Flags> targetFlags,
+ std::optional<nsecs_t> firstDownTimeInTarget) const {
+ std::shared_ptr<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());
+ if (inputChannel == nullptr) {
+ ALOGW("Not creating InputTarget for %s, no input channel", windowHandle->getName().c_str());
+ return {};
+ }
+ InputTarget inputTarget;
+ inputTarget.inputChannel = inputChannel;
+ inputTarget.flags = targetFlags;
+ inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor;
+ inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
+ const auto& displayInfoIt = mDisplayInfos.find(windowHandle->getInfo()->displayId);
+ if (displayInfoIt != mDisplayInfos.end()) {
+ inputTarget.displayTransform = displayInfoIt->second.transform;
+ } else {
+ // DisplayInfo not found for this window on display windowInfo->displayId.
+ // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed.
+ }
+ return inputTarget;
+}
+
void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
ftl::Flags<InputTarget::Flags> targetFlags,
std::bitset<MAX_POINTER_ID + 1> pointerIds,
@@ -2826,25 +2858,12 @@
const WindowInfo* windowInfo = windowHandle->getInfo();
if (it == inputTargets.end()) {
- InputTarget inputTarget;
- std::shared_ptr<InputChannel> inputChannel =
- getInputChannelLocked(windowHandle->getToken());
- if (inputChannel == nullptr) {
- ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str());
+ std::optional<InputTarget> target =
+ createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget);
+ if (!target) {
return;
}
- inputTarget.inputChannel = inputChannel;
- inputTarget.flags = targetFlags;
- inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
- inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
- const auto& displayInfoIt = mDisplayInfos.find(windowInfo->displayId);
- if (displayInfoIt != mDisplayInfos.end()) {
- inputTarget.displayTransform = displayInfoIt->second.transform;
- } else {
- // DisplayInfo not found for this window on display windowInfo->displayId.
- // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed.
- }
- inputTargets.push_back(inputTarget);
+ inputTargets.push_back(*target);
it = inputTargets.end() - 1;
}
@@ -5640,14 +5659,6 @@
} else {
dump += INDENT "Displays: <none>\n";
}
- dump += INDENT "Window Infos:\n";
- dump += StringPrintf(INDENT2 "vsync id: %" PRId64 "\n", mWindowInfosVsyncId);
- dump += StringPrintf(INDENT2 "timestamp (ns): %" PRId64 "\n", mWindowInfosTimestamp);
- dump += "\n";
- dump += StringPrintf(INDENT2 "max update delay (ns): %" PRId64 "\n", mMaxWindowInfosDelay);
- dump += StringPrintf(INDENT2 "max update delay vsync id: %" PRId64 "\n",
- mMaxWindowInfosDelayVsyncId);
- dump += "\n";
if (!mGlobalMonitorsByDisplay.empty()) {
for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) {
@@ -6689,15 +6700,6 @@
for (const auto& [displayId, handles] : handlesPerDisplay) {
setInputWindowsLocked(handles, displayId);
}
-
- mWindowInfosVsyncId = update.vsyncId;
- mWindowInfosTimestamp = update.timestamp;
-
- int64_t delay = systemTime() - update.timestamp;
- if (delay > mMaxWindowInfosDelay) {
- mMaxWindowInfosDelay = delay;
- mMaxWindowInfosDelayVsyncId = update.vsyncId;
- }
}
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 9b12f2f..8ca01b7 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -204,11 +204,6 @@
const IdGenerator mIdGenerator;
- int64_t mWindowInfosVsyncId GUARDED_BY(mLock);
- int64_t mWindowInfosTimestamp GUARDED_BY(mLock);
- int64_t mMaxWindowInfosDelay GUARDED_BY(mLock) = -1;
- int64_t mMaxWindowInfosDelayVsyncId GUARDED_BY(mLock) = -1;
-
// With each iteration, InputDispatcher nominally processes one queued event,
// a timeout, or a response from an input consumer.
// This method should only be called on the input dispatcher's own thread.
@@ -556,6 +551,10 @@
std::vector<Monitor> selectResponsiveMonitorsLocked(
const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
+ std::optional<InputTarget> createInputTargetLocked(
+ const sp<android::gui::WindowInfoHandle>& windowHandle,
+ ftl::Flags<InputTarget::Flags> targetFlags,
+ std::optional<nsecs_t> firstDownTimeInTarget) const REQUIRES(mLock);
void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
ftl::Flags<InputTarget::Flags> targetFlags,
std::bitset<MAX_POINTER_ID + 1> pointerIds,
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index c8c5115..c6b93a5 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -151,21 +151,22 @@
return;
}
std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
- std::vector<std::unique_ptr<InputMapper>> mappers;
-
- mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
+ mDevices.insert(
+ {eventHubId,
+ std::make_pair<std::unique_ptr<InputDeviceContext>,
+ std::vector<std::unique_ptr<InputMapper>>>(std::move(contextPtr), {})});
}
-void InputDevice::addEventHubDevice(int32_t eventHubId,
- const InputReaderConfiguration& readerConfig) {
- if (mDevices.find(eventHubId) != mDevices.end()) {
- return;
- }
- std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
- std::vector<std::unique_ptr<InputMapper>> mappers = createMappers(*contextPtr, readerConfig);
-
- // insert the context into the devices set
- mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
+void InputDevice::populateMappers(int32_t eventHubId,
+ const InputReaderConfiguration& readerConfig) {
+ auto targetDevice = mDevices.find(eventHubId);
+ LOG_ALWAYS_FATAL_IF(targetDevice == mDevices.end(),
+ "InputDevice::populateMappers(): missing device with eventHubId %d ",
+ eventHubId);
+ // create and add mappers to device
+ InputDeviceContext& context = *targetDevice->second.first;
+ std::vector<std::unique_ptr<InputMapper>> mappers = createMappers(context, readerConfig);
+ targetDevice->second.second = std::move(mappers);
// Must change generation to flag this device as changed
bumpGeneration();
}
@@ -440,29 +441,29 @@
}
std::vector<std::unique_ptr<InputMapper>> InputDevice::createMappers(
- InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig) {
- ftl::Flags<InputDeviceClass> classes = contextPtr.getDeviceClasses();
+ InputDeviceContext& context, const InputReaderConfiguration& readerConfig) {
+ ftl::Flags<InputDeviceClass> classes = context.getDeviceClasses();
std::vector<std::unique_ptr<InputMapper>> mappers;
// Switch-like devices.
if (classes.test(InputDeviceClass::SWITCH)) {
- mappers.push_back(createInputMapper<SwitchInputMapper>(contextPtr, readerConfig));
+ mappers.push_back(createInputMapper<SwitchInputMapper>(context, readerConfig));
}
// Scroll wheel-like devices.
if (classes.test(InputDeviceClass::ROTARY_ENCODER)) {
- mappers.push_back(createInputMapper<RotaryEncoderInputMapper>(contextPtr, readerConfig));
+ mappers.push_back(createInputMapper<RotaryEncoderInputMapper>(context, readerConfig));
}
// Vibrator-like devices.
if (classes.test(InputDeviceClass::VIBRATOR)) {
- mappers.push_back(createInputMapper<VibratorInputMapper>(contextPtr, readerConfig));
+ mappers.push_back(createInputMapper<VibratorInputMapper>(context, readerConfig));
}
// Battery-like devices or light-containing devices.
// PeripheralController will be created with associated EventHub device.
if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) {
- mController = std::make_unique<PeripheralController>(contextPtr);
+ mController = std::make_unique<PeripheralController>(context);
}
// Keyboard-like devices.
@@ -482,13 +483,13 @@
}
if (keyboardSource != 0) {
- mappers.push_back(std::make_unique<KeyboardInputMapper>(contextPtr, readerConfig,
- keyboardSource, keyboardType));
+ mappers.push_back(createInputMapper<KeyboardInputMapper>(context, readerConfig,
+ keyboardSource, keyboardType));
}
// Cursor-like devices.
if (classes.test(InputDeviceClass::CURSOR)) {
- mappers.push_back(createInputMapper<CursorInputMapper>(contextPtr, readerConfig));
+ mappers.push_back(createInputMapper<CursorInputMapper>(context, readerConfig));
}
// Touchscreens and touchpad devices.
@@ -496,31 +497,31 @@
sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true);
// TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or
// at least load this setting from the IDC file.
- const InputDeviceIdentifier identifier = contextPtr.getDeviceIdentifier();
+ const InputDeviceIdentifier identifier = context.getDeviceIdentifier();
const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c &&
(identifier.product == 0x05c4 || identifier.product == 0x09cc);
if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) {
- mappers.push_back(createInputMapper<TouchpadInputMapper>(contextPtr, readerConfig));
+ mappers.push_back(createInputMapper<TouchpadInputMapper>(context, readerConfig));
} else if (classes.test(InputDeviceClass::TOUCH_MT)) {
- mappers.push_back(std::make_unique<MultiTouchInputMapper>(contextPtr, readerConfig));
+ mappers.push_back(createInputMapper<MultiTouchInputMapper>(context, readerConfig));
} else if (classes.test(InputDeviceClass::TOUCH)) {
- mappers.push_back(std::make_unique<SingleTouchInputMapper>(contextPtr, readerConfig));
+ mappers.push_back(createInputMapper<SingleTouchInputMapper>(context, readerConfig));
}
// Joystick-like devices.
if (classes.test(InputDeviceClass::JOYSTICK)) {
- mappers.push_back(createInputMapper<JoystickInputMapper>(contextPtr, readerConfig));
+ mappers.push_back(createInputMapper<JoystickInputMapper>(context, readerConfig));
}
// Motion sensor enabled devices.
if (classes.test(InputDeviceClass::SENSOR)) {
- mappers.push_back(createInputMapper<SensorInputMapper>(contextPtr, readerConfig));
+ mappers.push_back(createInputMapper<SensorInputMapper>(context, readerConfig));
}
// External stylus-like devices.
if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) {
- mappers.push_back(createInputMapper<ExternalStylusInputMapper>(contextPtr, readerConfig));
+ mappers.push_back(createInputMapper<ExternalStylusInputMapper>(context, readerConfig));
}
return mappers;
}
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index ea95f78..8a33dff 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -334,7 +334,9 @@
device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(),
identifier);
}
- device->addEventHubDevice(eventHubId, mConfig);
+ device->addEmptyEventHubDevice(eventHubId);
+ auto unused = device->configure(systemTime(SYSTEM_TIME_MONOTONIC), mConfig, /*changes=*/{});
+ device->populateMappers(eventHubId, mConfig);
return device;
}
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 0b8a608..1729d46 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -81,7 +81,7 @@
void dump(std::string& dump, const std::string& eventHubDevStr);
void addEmptyEventHubDevice(int32_t eventHubId);
- void addEventHubDevice(int32_t eventHubId, const InputReaderConfiguration& readerConfig);
+ void populateMappers(int32_t eventHubId, const InputReaderConfiguration& readerConfig);
void removeEventHubDevice(int32_t eventHubId);
[[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
const InputReaderConfiguration& readerConfig,
@@ -203,7 +203,7 @@
int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
std::vector<std::unique_ptr<InputMapper>> createMappers(
- InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig);
+ InputDeviceContext& context, const InputReaderConfiguration& readerConfig);
PropertyMap mConfiguration;
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index bd27383..cd3d3c4 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -23,9 +23,10 @@
class KeyboardInputMapper : public InputMapper {
public:
- KeyboardInputMapper(InputDeviceContext& deviceContext,
- const InputReaderConfiguration& readerConfig, uint32_t source,
- int32_t keyboardType);
+ template <class T, class... Args>
+ friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig,
+ Args... args);
~KeyboardInputMapper() override = default;
uint32_t getSources() const override;
@@ -82,6 +83,9 @@
bool doNotWakeByDefault{};
} mParameters{};
+ KeyboardInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig, uint32_t source,
+ int32_t keyboardType);
void configureParameters();
void dumpParameters(std::string& dump) const;
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index f300ee1..1d788df 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -27,8 +27,6 @@
friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
const InputReaderConfiguration& readerConfig,
Args... args);
- explicit MultiTouchInputMapper(InputDeviceContext& deviceContext,
- const InputReaderConfiguration& readerConfig);
~MultiTouchInputMapper() override;
@@ -41,6 +39,8 @@
bool hasStylus() const override;
private:
+ explicit MultiTouchInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig);
// simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this
// mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device.
// It is used to simulate stylus events for debugging and testing on a device that does not
diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
index dac53cf..7726bfb 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
@@ -27,8 +27,6 @@
friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
const InputReaderConfiguration& readerConfig,
Args... args);
- explicit SingleTouchInputMapper(InputDeviceContext& deviceContext,
- const InputReaderConfiguration& readerConfig);
~SingleTouchInputMapper() override;
@@ -42,6 +40,8 @@
private:
SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
+ explicit SingleTouchInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig);
};
} // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 3f2658a..017f10b 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -6592,6 +6592,29 @@
consumeMotionEvent(mWindow1, ACTION_MOVE, {{150, 150}});
}
+/**
+ * When hover starts in one window and continues into the other, there should be a HOVER_EXIT and
+ * a HOVER_ENTER generated, even if the windows have the same token. This is because the new window
+ * that the pointer is hovering over may have a different transform.
+ */
+TEST_F(InputDispatcherMultiWindowSameTokenTests, HoverIntoClone) {
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}});
+
+ // Start hover in window 1
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {{50, 50}}));
+ consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER,
+ {getPointInWindow(mWindow1->getInfo(), PointF{50, 50})});
+
+ // Move hover to window 2.
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {{150, 150}}));
+
+ consumeMotionEvent(mWindow1, ACTION_HOVER_EXIT, {{50, 50}});
+ consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER,
+ {getPointInWindow(mWindow2->getInfo(), PointF{150, 150})});
+}
+
class InputDispatcherSingleWindowAnr : public InputDispatcherTest {
virtual void SetUp() override {
InputDispatcherTest::SetUp();
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 9fbe762..c045e15 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2584,7 +2584,10 @@
mFakeEventHub->addDevice(TEST_EVENTHUB_ID, "Test EventHub device", InputDeviceClass::BATTERY);
InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{});
- device.addEventHubDevice(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration());
+ device.addEmptyEventHubDevice(TEST_EVENTHUB_ID);
+ auto unused = device.configure(systemTime(SYSTEM_TIME_MONOTONIC),
+ mFakePolicy->getReaderConfiguration(), /*changes=*/{});
+ device.populateMappers(TEST_EVENTHUB_ID, mFakePolicy->getReaderConfiguration());
device.removeEventHubDevice(TEST_EVENTHUB_ID);
std::string dumpStr, eventHubDevStr;
device.dump(dumpStr, eventHubDevStr);
@@ -2892,7 +2895,7 @@
TEST_F(KeyboardInputMapperTest, GetSources) {
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper.getSources());
@@ -2908,7 +2911,7 @@
mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, POLICY_FLAG_WAKE);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Initial metastate is AMETA_NONE.
ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3009,7 +3012,7 @@
mFakeEventHub->addKeyRemapping(EVENTHUB_ID, AKEYCODE_A, AKEYCODE_B);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Key down by scan code.
@@ -3031,7 +3034,7 @@
mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
NotifyKeyArgs args;
@@ -3054,7 +3057,7 @@
mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Initial metastate is AMETA_NONE.
@@ -3095,7 +3098,7 @@
mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
prepareDisplay(ui::ROTATION_90);
@@ -3117,7 +3120,7 @@
addConfigurationProperty("keyboard.orientationAware", "1");
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
prepareDisplay(ui::ROTATION_0);
@@ -3189,7 +3192,7 @@
mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
NotifyKeyArgs args;
@@ -3215,7 +3218,7 @@
addConfigurationProperty("keyboard.orientationAware", "1");
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
NotifyKeyArgs args;
@@ -3243,7 +3246,7 @@
TEST_F(KeyboardInputMapperTest, GetKeyCodeState) {
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 1);
@@ -3255,7 +3258,7 @@
TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) {
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z);
@@ -3268,7 +3271,7 @@
TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 1);
@@ -3280,7 +3283,7 @@
TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) {
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
@@ -3300,7 +3303,7 @@
mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Initial metastate is AMETA_NONE.
ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3366,7 +3369,7 @@
mFakeEventHub->addKey(EVENTHUB_ID, BTN_Y, 0, AKEYCODE_BUTTON_Y, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
// Meta state should be AMETA_NONE after reset
@@ -3416,14 +3419,16 @@
mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
KeyboardInputMapper& mapper2 =
- device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
- mFakePolicy->getReaderConfiguration(),
- AINPUT_SOURCE_KEYBOARD,
- AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+ mFakePolicy
+ ->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
std::list<NotifyArgs> unused =
device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
/*changes=*/{});
@@ -3485,7 +3490,7 @@
mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Initial metastate is AMETA_NONE.
ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3531,11 +3536,13 @@
mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+ device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
KeyboardInputMapper& mapper2 =
- device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
- mFakePolicy->getReaderConfiguration(),
- AINPUT_SOURCE_KEYBOARD,
- AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+ mFakePolicy
+ ->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
std::list<NotifyArgs> unused =
device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
/*changes=*/{});
@@ -3554,10 +3561,10 @@
mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
// Suppose we have two mappers. (DPAD + KEYBOARD)
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_DPAD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD,
AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Initial metastate is AMETA_NONE.
ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3576,7 +3583,7 @@
mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
KeyboardInputMapper& mapper1 =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// keyboard 2.
@@ -3594,11 +3601,13 @@
mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+ device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
KeyboardInputMapper& mapper2 =
- device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
- mFakePolicy->getReaderConfiguration(),
- AINPUT_SOURCE_KEYBOARD,
- AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+ mFakePolicy
+ ->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
std::list<NotifyArgs> unused =
device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
/*changes=*/{});
@@ -3654,7 +3663,7 @@
mFakeEventHub->addKey(EVENTHUB_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Key down by scan code.
process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
@@ -3680,9 +3689,8 @@
}
TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) {
- mDevice->addMapper<KeyboardInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
- AINPUT_SOURCE_KEYBOARD,
- AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
std::list<NotifyArgs> unused =
mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
/*changes=*/{});
@@ -3713,7 +3721,7 @@
RawLayoutInfo{.languageTag = "en", .layoutType = "extended"});
// Configuration
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
InputReaderConfiguration config;
std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
@@ -3739,7 +3747,7 @@
POLICY_FLAG_WAKE);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
@@ -3777,7 +3785,7 @@
addConfigurationProperty("keyboard.doNotWakeByDefault", "1");
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
diff --git a/services/surfaceflinger/BackgroundExecutor.cpp b/services/surfaceflinger/BackgroundExecutor.cpp
index a15de2b..6ddf790 100644
--- a/services/surfaceflinger/BackgroundExecutor.cpp
+++ b/services/surfaceflinger/BackgroundExecutor.cpp
@@ -28,29 +28,19 @@
ANDROID_SINGLETON_STATIC_INSTANCE(BackgroundExecutor);
BackgroundExecutor::BackgroundExecutor() : Singleton<BackgroundExecutor>() {
+ // mSemaphore must be initialized before any calls to
+ // BackgroundExecutor::sendCallbacks. For this reason, we initialize it
+ // within the constructor instead of within mThread.
+ LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed");
mThread = std::thread([&]() {
- LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed");
while (!mDone) {
LOG_ALWAYS_FATAL_IF(sem_wait(&mSemaphore), "sem_wait failed (%d)", errno);
-
- ftl::SmallVector<Work*, 10> workItems;
-
- Work* work = mWorks.pop();
- while (work) {
- workItems.push_back(work);
- work = mWorks.pop();
+ auto callbacks = mCallbacksQueue.pop();
+ if (!callbacks) {
+ continue;
}
-
- // Sequence numbers are guaranteed to be in intended order, as we assume a single
- // producer and single consumer.
- std::stable_sort(workItems.begin(), workItems.end(), [](Work* left, Work* right) {
- return left->sequence < right->sequence;
- });
- for (Work* work : workItems) {
- for (auto& task : work->tasks) {
- task();
- }
- delete work;
+ for (auto& callback : *callbacks) {
+ callback();
}
}
});
@@ -66,12 +56,8 @@
}
void BackgroundExecutor::sendCallbacks(Callbacks&& tasks) {
- Work* work = new Work();
- work->sequence = mSequence;
- work->tasks = std::move(tasks);
- mWorks.push(work);
- mSequence++;
+ mCallbacksQueue.push(std::move(tasks));
LOG_ALWAYS_FATAL_IF(sem_post(&mSemaphore), "sem_post failed");
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/BackgroundExecutor.h b/services/surfaceflinger/BackgroundExecutor.h
index eeaf3bd..0fae5a5 100644
--- a/services/surfaceflinger/BackgroundExecutor.h
+++ b/services/surfaceflinger/BackgroundExecutor.h
@@ -16,15 +16,13 @@
#pragma once
-#include <Tracing/LocklessStack.h>
-#include <android-base/thread_annotations.h>
#include <ftl/small_vector.h>
#include <semaphore.h>
#include <utils/Singleton.h>
-#include <mutex>
-#include <queue>
#include <thread>
+#include "LocklessQueue.h"
+
namespace android {
// Executes tasks off the main thread.
@@ -34,24 +32,14 @@
~BackgroundExecutor();
using Callbacks = ftl::SmallVector<std::function<void()>, 10>;
// Queues callbacks onto a work queue to be executed by a background thread.
- // Note that this is not thread-safe - a single producer is assumed.
+ // This is safe to call from multiple threads.
void sendCallbacks(Callbacks&& tasks);
private:
sem_t mSemaphore;
std::atomic_bool mDone = false;
- // Sequence number for work items.
- // Work items are batched by sequence number. Work items for earlier sequence numbers are
- // executed first. Work items with the same sequence number are executed in the same order they
- // were added to the stack (meaning the stack must reverse the order after popping from the
- // queue)
- int32_t mSequence = 0;
- struct Work {
- int32_t sequence = 0;
- Callbacks tasks;
- };
- LocklessStack<Work> mWorks;
+ LocklessQueue<Callbacks> mCallbacksQueue;
std::thread mThread;
};
diff --git a/services/surfaceflinger/FrontEnd/readme.md b/services/surfaceflinger/FrontEnd/readme.md
new file mode 100644
index 0000000..e5f51a5
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/readme.md
@@ -0,0 +1,110 @@
+# SurfaceFlinger FrontEnd
+
+SurfaceFlinger FrontEnd implements the client APIs that describe how buffers should be
+composited on the screen. Layers are used to capture how the buffer should be composited
+and each buffer is associated with a Layer. Transactions contain an atomic set of changes
+to one or more of these layers. The FrontEnd consumes these transactions, maintains the
+layer lifecycle, and provides a snapshot to the composition engine every frame that
+describes how a set of buffers should be composited.
+
+
+
+## Layers
+Layers are used to describe how a buffer should be placed on the display relative to other
+buffers. They are represented as a hierarchy, similar to a scene graph. Child layers can
+inherit some properties from their parents, which allows higher-level system components to
+maintain policies at different levels without needing to understand the entire hierarchy.
+This allows control to be delegated to different parts of the system - such as SystemServer,
+SysUI and Apps.
+
+### Layer Lifecycle
+Layer is created by a client. The client receives a strong binder reference to the layer
+handle, which will keep the layer alive as long as the client holds the reference. The
+layer can also be kept alive if the layer has a parent, since the parent will hold a
+strong reference to the children. If the layer is not reachable but its handle is alive,
+the layer will be offscreen and its resources will not be freed. Clients must explicitly
+release all references to the handle as soon as it's done with the layer. It's strongly
+recommended to explicitly release the layer in Java and not rely on the GC.
+
+
+
+## Transactions
+Transactions contain a group of changes to one or more layers that are applied together.
+Transactions can be merged to apply a set of changes atomically. Merges are associative,
+meaning how you group the merges does not matter, but they are not commutative, meaning
+that the order in which you merge them does.
+For example:
+
+`Transaction a; a.setAlpha(sc, 2);`
+
+`Transaction b; b.setAlpha(sc, 4);`
+
+`a.merge(b)` is not the same as `b.merge(a)`
+
+<p>
+
+`Transaction c; c.setAlpha(sc, 6);`
+
+`a.merge(b).merge(c)` is the same as `b.merge(c); a.merge(b);`
+
+Transactions are queued in SurfaceFlinger per ApplyToken so order is only guaranteed for
+Transactions with the same applyToken. By default each process and each buffer producer
+provides a unique ApplyToken. This prevents clients from affecting one another, and possibly
+slowing each other down.
+
+
+
+## Architecture
+SurfaceFlinger FrontEnd intends to optimize for predictability and performance because state
+generation is on the hotpath. Simple buffer updates should be as fast as possible, and they
+should be consistently fast. This means avoiding contention (e.g., locks) and context
+switching. We also want to avoid doing anything that does not contribute to putting a pixel
+on the display.
+
+The pipeline can be broken down into five stages:
+- Queue and filter transactions that are ready to be committed.
+- Handle layer lifecycles and update server-side state per layer.
+- Generate and/or update the traversal trees.
+- Generate a z-ordered list of snapshots.
+- Emit callbacks back to clients
+
+
+### TransactionHandler
+TransactionHandler is responsible for queuing and filtering transactions that are ready to
+be applied. On commit, we filter the transactions that are ready. We provide an interface
+for other components to apply their own filter to determine if a transaction is ready to be
+applied.
+
+
+### LayerLifecycleManager
+RequestedLayerState is a simple data class that stores the server side layer state.
+Transactions are merged into this state, similar to how transactions can be merged on the
+client side. The states can always be reconstructed from LayerCreationArgs and a list of
+transactions. LayerLifecycleManager keeps track of Layer handle lifecycle and the layer
+lifecycle itself. It consumes a list of transactions and generates a list of server side
+states and change flags. Other components can register to listen to layer lifecycles.
+
+
+### LayerHierarchyBuilder
+LayerHierarchyBuilder consumes a list of RequestedLayerStates to generate a LayerHierarchy.
+The hierarchy provides functions for breadth-first traversal and z-order traversal of the
+entire tree or a subtree. Internally, the hierarchy is represented by a graph. Mirrored
+layers are represented by the same node in the graph with multiple parents. This allows us
+to implement mirroring without cloning Layers and maintaining complex hierarchies.
+
+
+### LayerSnapshotBuilder
+LayerSnapshotBuilder consumes a LayerHierarchy along with a list of RequestedLayerStates to
+generate a flattened z-ordered list of LayerSnapshots. LayerSnapshots contain all the data
+required for CompositionEngine and RenderEngine. It has no dependencies to FrontEnd, or the
+LayerHierarchy used to create them. They can be cloned and consumed freely. Other consumers
+like WindowInfo listeners (input and accessibility) also updated from these snapshots.
+
+Change flags are used to efficiently traverse this hierarchy where possible. This allows us
+to support short circuiting parts of the hierarchy, partial hierarchy updates and fast paths
+for buffer updates.
+
+
+While they can be cloned, the current implementation moves the snapshot from FrontEnd to
+CompositionEngine to avoid needless work in the hotpath. For snapshot consumers not critical
+to composition, the goal is to clone the snapshots and consume them on a background thread.
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 38590e6..f7596e2 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -877,6 +877,7 @@
// TODO(b/238781169) Remove direct calls to RenderEngine::drawLayers that don't go through
// CompositionEngine to create a single path for composing layers.
void updateSnapshot(bool updateGeometry);
+ void updateChildrenSnapshots(bool updateGeometry);
void updateMetadataSnapshot(const LayerMetadata& parentMetadata);
void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata,
std::unordered_set<Layer*>& visited);
@@ -1134,8 +1135,6 @@
bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); }
- void updateChildrenSnapshots(bool updateGeometry);
-
// Fills the provided vector with the currently available JankData and removes the processed
// JankData from the pending list.
void transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index d606cff..51d4ff8 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -116,6 +116,8 @@
mLayer->setChildrenDrawingParent(mLayer);
}
}
+ mLayer->updateSnapshot(/*updateGeometry=*/true);
+ mLayer->updateChildrenSnapshots(/*updateGeometry=*/true);
}
} // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index d55ec99..2ac1db9 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2547,7 +2547,7 @@
}
updateCursorAsync();
- updateInputFlinger(vsyncId);
+ updateInputFlinger(vsyncId, frameTime);
if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
// This will block and tracing should only be enabled for debugging.
@@ -2674,12 +2674,15 @@
mTimeStats->recordFrameDuration(frameTime.ns(), systemTime());
- // Send a power hint hint after presentation is finished
+ // Send a power hint after presentation is finished.
if (mPowerHintSessionEnabled) {
- const nsecs_t pastPresentTime =
- getPreviousPresentFence(frameTime, vsyncPeriod)->getSignalTime();
+ // Now that the current frame has been presented above, PowerAdvisor needs the present time
+ // of the previous frame (whose fence is signaled by now) to determine how long the HWC had
+ // waited on that fence to retire before presenting.
+ const auto& previousPresentFence = mPreviousPresentFences[0].fenceTime;
- mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(pastPresentTime), TimePoint::now());
+ mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(previousPresentFence->getSignalTime()),
+ TimePoint::now());
mPowerAdvisor->reportActualWorkDuration();
}
@@ -3737,7 +3740,7 @@
doCommitTransactions();
}
-void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId) {
+void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId, TimePoint frameTime) {
if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) {
return;
}
@@ -3749,8 +3752,6 @@
if (mUpdateInputInfo) {
mUpdateInputInfo = false;
updateWindowInfo = true;
- mLastInputFlingerUpdateVsyncId = vsyncId;
- mLastInputFlingerUpdateTimestamp = systemTime();
buildWindowInfos(windowInfos, displayInfos);
}
@@ -3772,17 +3773,17 @@
inputWindowCommands =
std::move(mInputWindowCommands),
inputFlinger = mInputFlinger, this,
- visibleWindowsChanged]() {
+ visibleWindowsChanged, vsyncId, frameTime]() {
ATRACE_NAME("BackgroundExecutor::updateInputFlinger");
if (updateWindowInfo) {
mWindowInfosListenerInvoker
- ->windowInfosChanged(std::move(windowInfos), std::move(displayInfos),
+ ->windowInfosChanged(gui::WindowInfosUpdate{std::move(windowInfos),
+ std::move(displayInfos),
+ vsyncId.value, frameTime.ns()},
std::move(
inputWindowCommands.windowInfosReportedListeners),
/* forceImmediateCall= */ visibleWindowsChanged ||
- !inputWindowCommands.focusRequests.empty(),
- mLastInputFlingerUpdateVsyncId,
- mLastInputFlingerUpdateTimestamp);
+ !inputWindowCommands.focusRequests.empty());
} else {
// If there are listeners but no changes to input windows, call the listeners
// immediately.
@@ -6149,27 +6150,14 @@
result.append("\n");
result.append("Window Infos:\n");
- StringAppendF(&result, " input flinger update vsync id: %" PRId64 "\n",
- mLastInputFlingerUpdateVsyncId.value);
- StringAppendF(&result, " input flinger update timestamp (ns): %" PRId64 "\n",
- mLastInputFlingerUpdateTimestamp);
+ auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo();
+ StringAppendF(&result, " max send vsync id: %" PRId64 "\n",
+ windowInfosDebug.maxSendDelayVsyncId.value);
+ StringAppendF(&result, " max send delay (ns): %" PRId64 " ns\n",
+ windowInfosDebug.maxSendDelayDuration);
+ StringAppendF(&result, " unsent messages: %" PRIu32 "\n",
+ windowInfosDebug.pendingMessageCount);
result.append("\n");
-
- if (int64_t unsentVsyncId = mWindowInfosListenerInvoker->getUnsentMessageVsyncId().value;
- unsentVsyncId != -1) {
- StringAppendF(&result, " unsent input flinger update vsync id: %" PRId64 "\n",
- unsentVsyncId);
- StringAppendF(&result, " unsent input flinger update timestamp (ns): %" PRId64 "\n",
- mWindowInfosListenerInvoker->getUnsentMessageTimestamp());
- result.append("\n");
- }
-
- if (uint32_t pendingMessages = mWindowInfosListenerInvoker->getPendingMessageCount();
- pendingMessages != 0) {
- StringAppendF(&result, " pending input flinger calls: %" PRIu32 "\n",
- mWindowInfosListenerInvoker->getPendingMessageCount());
- result.append("\n");
- }
}
mat4 SurfaceFlinger::calculateColorMatrix(float saturation) {
@@ -6992,7 +6980,8 @@
const auto dataspaceForColorMode = ui::pickDataspaceFor(state.colorMode);
- if (capturingHdrLayers && !hintForSeamlessTransition) {
+ // TODO: Enable once HDR screenshots are ready.
+ if constexpr (/* DISABLES CODE */ (false)) {
// For now since we only support 8-bit screenshots, just use HLG and
// assume that 1.0 >= display max luminance. This isn't quite as future
// proof as PQ is, but is good enough.
@@ -7045,9 +7034,9 @@
}
RenderAreaFuture renderAreaFuture = ftl::defer([=] {
- return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize,
- ui::Dataspace::UNKNOWN, args.useIdentityTransform,
- args.hintForSeamlessTransition, args.captureSecureLayers);
+ return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, args.dataspace,
+ args.useIdentityTransform, args.hintForSeamlessTransition,
+ args.captureSecureLayers);
});
GetLayerSnapshotsFunction getLayerSnapshots;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index e2691ab..0bc506f 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -722,7 +722,7 @@
void updateLayerHistory(const frontend::LayerSnapshot& snapshot);
frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext);
- void updateInputFlinger(VsyncId);
+ void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime);
void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
void buildWindowInfos(std::vector<gui::WindowInfo>& outWindowInfos,
std::vector<gui::DisplayInfo>& outDisplayInfos);
@@ -1259,9 +1259,6 @@
VsyncId mLastCommittedVsyncId;
- VsyncId mLastInputFlingerUpdateVsyncId;
- nsecs_t mLastInputFlingerUpdateTimestamp;
-
// If blurs should be enabled on this device.
bool mSupportsBlur = false;
std::atomic<uint32_t> mFrameMissedCount = 0;
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index 2b62638..20699ef 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -16,8 +16,11 @@
#include <ftl/small_vector.h>
#include <gui/ISurfaceComposer.h>
+#include <gui/TraceUtils.h>
#include <gui/WindowInfosUpdate.h>
+#include <scheduler/Time.h>
+#include "BackgroundExecutor.h"
#include "WindowInfosListenerInvoker.h"
namespace android {
@@ -26,7 +29,7 @@
using gui::IWindowInfosListener;
using gui::WindowInfo;
-using WindowInfosListenerVector = ftl::SmallVector<const sp<IWindowInfosListener>, 3>;
+using WindowInfosListenerVector = ftl::SmallVector<const sp<gui::IWindowInfosListener>, 3>;
struct WindowInfosReportedListenerInvoker : gui::BnWindowInfosReportedListener,
IBinder::DeathRecipient {
@@ -86,45 +89,19 @@
}
void WindowInfosListenerInvoker::windowInfosChanged(
- std::vector<WindowInfo> windowInfos, std::vector<DisplayInfo> displayInfos,
- WindowInfosReportedListenerSet reportedListeners, bool forceImmediateCall, VsyncId vsyncId,
- nsecs_t timestamp) {
- reportedListeners.insert(sp<WindowInfosListenerInvoker>::fromExisting(this));
- auto callListeners = [this, windowInfos = std::move(windowInfos),
- displayInfos = std::move(displayInfos), vsyncId,
- timestamp](WindowInfosReportedListenerSet reportedListeners) mutable {
- WindowInfosListenerVector windowInfosListeners;
- {
- std::scoped_lock lock(mListenersMutex);
- for (const auto& [_, listener] : mWindowInfosListeners) {
- windowInfosListeners.push_back(listener);
- }
- }
-
- auto reportedInvoker =
- sp<WindowInfosReportedListenerInvoker>::make(windowInfosListeners,
- std::move(reportedListeners));
-
- gui::WindowInfosUpdate update(std::move(windowInfos), std::move(displayInfos),
- vsyncId.value, timestamp);
-
- for (const auto& listener : windowInfosListeners) {
- sp<IBinder> asBinder = IInterface::asBinder(listener);
-
- // linkToDeath is used here to ensure that the windowInfosReportedListeners
- // are called even if one of the windowInfosListeners dies before
- // calling onWindowInfosReported.
- asBinder->linkToDeath(reportedInvoker);
-
- auto status = listener->onWindowInfosChanged(update, reportedInvoker);
- if (!status.isOk()) {
- reportedInvoker->onWindowInfosReported();
- }
- }
- };
-
+ gui::WindowInfosUpdate update, WindowInfosReportedListenerSet reportedListeners,
+ bool forceImmediateCall) {
+ WindowInfosListenerVector listeners;
{
- std::scoped_lock lock(mMessagesMutex);
+ std::scoped_lock lock{mMessagesMutex};
+
+ if (!mDelayInfo) {
+ mDelayInfo = DelayInfo{
+ .vsyncId = update.vsyncId,
+ .frameTime = update.timestamp,
+ };
+ }
+
// If there are unacked messages and this isn't a forced call, then return immediately.
// If a forced window infos change doesn't happen first, the update will be sent after
// the WindowInfosReportedListeners are called. If a forced window infos change happens or
@@ -132,44 +109,87 @@
// will be dropped and the listeners will only be called with the latest info. This is done
// to reduce the amount of binder memory used.
if (mActiveMessageCount > 0 && !forceImmediateCall) {
- mWindowInfosChangedDelayed = std::move(callListeners);
- mUnsentVsyncId = vsyncId;
- mUnsentTimestamp = timestamp;
- mReportedListenersDelayed.merge(reportedListeners);
+ mDelayedUpdate = std::move(update);
+ mReportedListeners.merge(reportedListeners);
return;
}
- mWindowInfosChangedDelayed = nullptr;
- mUnsentVsyncId = {-1};
- mUnsentTimestamp = -1;
- reportedListeners.merge(mReportedListenersDelayed);
+ if (mDelayedUpdate) {
+ mDelayedUpdate.reset();
+ }
+
+ {
+ std::scoped_lock lock{mListenersMutex};
+ for (const auto& [_, listener] : mWindowInfosListeners) {
+ listeners.push_back(listener);
+ }
+ }
+ if (CC_UNLIKELY(listeners.empty())) {
+ mReportedListeners.merge(reportedListeners);
+ mDelayInfo.reset();
+ return;
+ }
+
+ reportedListeners.insert(sp<WindowInfosListenerInvoker>::fromExisting(this));
+ reportedListeners.merge(mReportedListeners);
+ mReportedListeners.clear();
+
mActiveMessageCount++;
+ updateMaxSendDelay();
+ mDelayInfo.reset();
}
- callListeners(std::move(reportedListeners));
+
+ auto reportedInvoker =
+ sp<WindowInfosReportedListenerInvoker>::make(listeners, std::move(reportedListeners));
+
+ for (const auto& listener : listeners) {
+ sp<IBinder> asBinder = IInterface::asBinder(listener);
+
+ // linkToDeath is used here to ensure that the windowInfosReportedListeners
+ // are called even if one of the windowInfosListeners dies before
+ // calling onWindowInfosReported.
+ asBinder->linkToDeath(reportedInvoker);
+
+ auto status = listener->onWindowInfosChanged(update, reportedInvoker);
+ if (!status.isOk()) {
+ reportedInvoker->onWindowInfosReported();
+ }
+ }
}
binder::Status WindowInfosListenerInvoker::onWindowInfosReported() {
- std::function<void(WindowInfosReportedListenerSet)> callListeners;
- WindowInfosReportedListenerSet reportedListeners;
-
- {
- std::scoped_lock lock{mMessagesMutex};
- mActiveMessageCount--;
- if (!mWindowInfosChangedDelayed || mActiveMessageCount > 0) {
- return binder::Status::ok();
+ BackgroundExecutor::getInstance().sendCallbacks({[this]() {
+ gui::WindowInfosUpdate update;
+ {
+ std::scoped_lock lock{mMessagesMutex};
+ mActiveMessageCount--;
+ if (!mDelayedUpdate || mActiveMessageCount > 0) {
+ return;
+ }
+ update = std::move(*mDelayedUpdate);
+ mDelayedUpdate.reset();
}
-
- mActiveMessageCount++;
- callListeners = std::move(mWindowInfosChangedDelayed);
- mWindowInfosChangedDelayed = nullptr;
- mUnsentVsyncId = {-1};
- mUnsentTimestamp = -1;
- reportedListeners = std::move(mReportedListenersDelayed);
- mReportedListenersDelayed.clear();
- }
-
- callListeners(std::move(reportedListeners));
+ windowInfosChanged(std::move(update), {}, false);
+ }});
return binder::Status::ok();
}
+WindowInfosListenerInvoker::DebugInfo WindowInfosListenerInvoker::getDebugInfo() {
+ std::scoped_lock lock{mMessagesMutex};
+ updateMaxSendDelay();
+ mDebugInfo.pendingMessageCount = mActiveMessageCount;
+ return mDebugInfo;
+}
+
+void WindowInfosListenerInvoker::updateMaxSendDelay() {
+ if (!mDelayInfo) {
+ return;
+ }
+ nsecs_t delay = TimePoint::now().ns() - mDelayInfo->frameTime;
+ if (delay > mDebugInfo.maxSendDelayDuration) {
+ mDebugInfo.maxSendDelayDuration = delay;
+ mDebugInfo.maxSendDelayVsyncId = VsyncId{mDelayInfo->vsyncId};
+ }
+}
+
} // namespace android
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h
index e35d056..bc465a3 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -16,6 +16,7 @@
#pragma once
+#include <optional>
#include <unordered_set>
#include <android/gui/BnWindowInfosReportedListener.h>
@@ -40,26 +41,18 @@
void addWindowInfosListener(sp<gui::IWindowInfosListener>);
void removeWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener);
- void windowInfosChanged(std::vector<gui::WindowInfo>, std::vector<gui::DisplayInfo>,
+ void windowInfosChanged(gui::WindowInfosUpdate update,
WindowInfosReportedListenerSet windowInfosReportedListeners,
- bool forceImmediateCall, VsyncId vsyncId, nsecs_t timestamp);
+ bool forceImmediateCall);
binder::Status onWindowInfosReported() override;
- VsyncId getUnsentMessageVsyncId() {
- std::scoped_lock lock(mMessagesMutex);
- return mUnsentVsyncId;
- }
-
- nsecs_t getUnsentMessageTimestamp() {
- std::scoped_lock lock(mMessagesMutex);
- return mUnsentTimestamp;
- }
-
- uint32_t getPendingMessageCount() {
- std::scoped_lock lock(mMessagesMutex);
- return mActiveMessageCount;
- }
+ struct DebugInfo {
+ VsyncId maxSendDelayVsyncId;
+ nsecs_t maxSendDelayDuration;
+ uint32_t pendingMessageCount;
+ };
+ DebugInfo getDebugInfo();
protected:
void binderDied(const wp<IBinder>& who) override;
@@ -73,11 +66,16 @@
std::mutex mMessagesMutex;
uint32_t mActiveMessageCount GUARDED_BY(mMessagesMutex) = 0;
- std::function<void(WindowInfosReportedListenerSet)> mWindowInfosChangedDelayed
- GUARDED_BY(mMessagesMutex);
- VsyncId mUnsentVsyncId GUARDED_BY(mMessagesMutex) = {-1};
- nsecs_t mUnsentTimestamp GUARDED_BY(mMessagesMutex) = -1;
- WindowInfosReportedListenerSet mReportedListenersDelayed;
+ std::optional<gui::WindowInfosUpdate> mDelayedUpdate GUARDED_BY(mMessagesMutex);
+ WindowInfosReportedListenerSet mReportedListeners;
+
+ DebugInfo mDebugInfo GUARDED_BY(mMessagesMutex);
+ struct DelayInfo {
+ int64_t vsyncId;
+ nsecs_t frameTime;
+ };
+ std::optional<DelayInfo> mDelayInfo GUARDED_BY(mMessagesMutex);
+ void updateMaxSendDelay() REQUIRES(mMessagesMutex);
};
} // namespace android
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index da5ec48..4d03be0 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -590,7 +590,7 @@
mFlinger->binderDied(display);
mFlinger->onFirstRef();
- mFlinger->updateInputFlinger(VsyncId{0});
+ mFlinger->updateInputFlinger(VsyncId{}, TimePoint{});
mFlinger->updateCursorAsync();
mutableScheduler().setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral<nsecs_t>(),
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 70f8a83..db81bad 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -71,6 +71,7 @@
":libsurfaceflinger_sources",
"libsurfaceflinger_unittest_main.cpp",
"ActiveDisplayRotationFlagsTest.cpp",
+ "BackgroundExecutorTest.cpp",
"CompositionTest.cpp",
"DisplayIdGeneratorTest.cpp",
"DisplayTransactionTest.cpp",
@@ -138,6 +139,7 @@
"VSyncReactorTest.cpp",
"VsyncConfigurationTest.cpp",
"VsyncScheduleTest.cpp",
+ "WindowInfosListenerInvokerTest.cpp",
],
}
diff --git a/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp b/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp
new file mode 100644
index 0000000..5413bae
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp
@@ -0,0 +1,57 @@
+#include <gtest/gtest.h>
+#include <condition_variable>
+
+#include "BackgroundExecutor.h"
+
+namespace android {
+
+class BackgroundExecutorTest : public testing::Test {};
+
+namespace {
+
+TEST_F(BackgroundExecutorTest, singleProducer) {
+ std::mutex mutex;
+ std::condition_variable condition_variable;
+ bool backgroundTaskComplete = false;
+
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&mutex, &condition_variable, &backgroundTaskComplete]() {
+ std::lock_guard<std::mutex> lock{mutex};
+ condition_variable.notify_one();
+ backgroundTaskComplete = true;
+ }});
+
+ std::unique_lock<std::mutex> lock{mutex};
+ condition_variable.wait(lock, [&backgroundTaskComplete]() { return backgroundTaskComplete; });
+ ASSERT_TRUE(backgroundTaskComplete);
+}
+
+TEST_F(BackgroundExecutorTest, multipleProducers) {
+ std::mutex mutex;
+ std::condition_variable condition_variable;
+ const int backgroundTaskCount = 10;
+ int backgroundTaskCompleteCount = 0;
+
+ for (int i = 0; i < backgroundTaskCount; i++) {
+ std::thread([&mutex, &condition_variable, &backgroundTaskCompleteCount]() {
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&mutex, &condition_variable, &backgroundTaskCompleteCount]() {
+ std::lock_guard<std::mutex> lock{mutex};
+ backgroundTaskCompleteCount++;
+ if (backgroundTaskCompleteCount == backgroundTaskCount) {
+ condition_variable.notify_one();
+ }
+ }});
+ }).detach();
+ }
+
+ std::unique_lock<std::mutex> lock{mutex};
+ condition_variable.wait(lock, [&backgroundTaskCompleteCount]() {
+ return backgroundTaskCompleteCount == backgroundTaskCount;
+ });
+ ASSERT_EQ(backgroundTaskCount, backgroundTaskCompleteCount);
+}
+
+} // namespace
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
new file mode 100644
index 0000000..af4971b
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
@@ -0,0 +1,244 @@
+#include <android/gui/BnWindowInfosListener.h>
+#include <gtest/gtest.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/WindowInfosUpdate.h>
+#include <condition_variable>
+
+#include "BackgroundExecutor.h"
+#include "WindowInfosListenerInvoker.h"
+#include "android/gui/IWindowInfosReportedListener.h"
+
+namespace android {
+
+class WindowInfosListenerInvokerTest : public testing::Test {
+protected:
+ WindowInfosListenerInvokerTest() : mInvoker(sp<WindowInfosListenerInvoker>::make()) {}
+
+ ~WindowInfosListenerInvokerTest() {
+ std::mutex mutex;
+ std::condition_variable cv;
+ bool flushComplete = false;
+ // Flush the BackgroundExecutor thread to ensure any scheduled tasks are complete.
+ // Otherwise, references those tasks hold may go out of scope before they are done
+ // executing.
+ BackgroundExecutor::getInstance().sendCallbacks({[&]() {
+ std::scoped_lock lock{mutex};
+ flushComplete = true;
+ cv.notify_one();
+ }});
+ std::unique_lock<std::mutex> lock{mutex};
+ cv.wait(lock, [&]() { return flushComplete; });
+ }
+
+ sp<WindowInfosListenerInvoker> mInvoker;
+};
+
+using WindowInfosUpdateConsumer = std::function<void(const gui::WindowInfosUpdate&,
+ const sp<gui::IWindowInfosReportedListener>&)>;
+
+class Listener : public gui::BnWindowInfosListener {
+public:
+ Listener(WindowInfosUpdateConsumer consumer) : mConsumer(std::move(consumer)) {}
+
+ binder::Status onWindowInfosChanged(
+ const gui::WindowInfosUpdate& update,
+ const sp<gui::IWindowInfosReportedListener>& reportedListener) override {
+ mConsumer(update, reportedListener);
+ return binder::Status::ok();
+ }
+
+private:
+ WindowInfosUpdateConsumer mConsumer;
+};
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged calls a single window infos listener.
+TEST_F(WindowInfosListenerInvokerTest, callsSingleListener) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int callCount = 0;
+
+ mInvoker->addWindowInfosListener(
+ sp<Listener>::make([&](const gui::WindowInfosUpdate&,
+ const sp<gui::IWindowInfosReportedListener>& reportedListener) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ cv.notify_one();
+
+ reportedListener->onWindowInfosReported();
+ }));
+
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[this]() { mInvoker->windowInfosChanged({}, {}, false); }});
+
+ std::unique_lock<std::mutex> lock{mutex};
+ cv.wait(lock, [&]() { return callCount == 1; });
+ EXPECT_EQ(callCount, 1);
+}
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged calls multiple window infos listeners.
+TEST_F(WindowInfosListenerInvokerTest, callsMultipleListeners) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int callCount = 0;
+ const int expectedCallCount = 3;
+
+ for (int i = 0; i < expectedCallCount; i++) {
+ mInvoker->addWindowInfosListener(sp<Listener>::make(
+ [&](const gui::WindowInfosUpdate&,
+ const sp<gui::IWindowInfosReportedListener>& reportedListener) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ if (callCount == expectedCallCount) {
+ cv.notify_one();
+ }
+
+ reportedListener->onWindowInfosReported();
+ }));
+ }
+
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+
+ std::unique_lock<std::mutex> lock{mutex};
+ cv.wait(lock, [&]() { return callCount == expectedCallCount; });
+ EXPECT_EQ(callCount, expectedCallCount);
+}
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged delays sending a second message until
+// after the WindowInfosReportedListener is called.
+TEST_F(WindowInfosListenerInvokerTest, delaysUnackedCall) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int callCount = 0;
+
+ // Simulate a slow ack by not calling the WindowInfosReportedListener.
+ mInvoker->addWindowInfosListener(sp<Listener>::make(
+ [&](const gui::WindowInfosUpdate&, const sp<gui::IWindowInfosReportedListener>&) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ cv.notify_one();
+ }));
+
+ BackgroundExecutor::getInstance().sendCallbacks({[&]() {
+ mInvoker->windowInfosChanged({}, {}, false);
+ mInvoker->windowInfosChanged({}, {}, false);
+ }});
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return callCount == 1; });
+ }
+ EXPECT_EQ(callCount, 1);
+
+ // Ack the first message.
+ mInvoker->onWindowInfosReported();
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return callCount == 2; });
+ }
+ EXPECT_EQ(callCount, 2);
+}
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged immediately sends a second message when
+// forceImmediateCall is true.
+TEST_F(WindowInfosListenerInvokerTest, sendsForcedMessage) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int callCount = 0;
+ const int expectedCallCount = 2;
+
+ // Simulate a slow ack by not calling the WindowInfosReportedListener.
+ mInvoker->addWindowInfosListener(sp<Listener>::make(
+ [&](const gui::WindowInfosUpdate&, const sp<gui::IWindowInfosReportedListener>&) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ if (callCount == expectedCallCount) {
+ cv.notify_one();
+ }
+ }));
+
+ BackgroundExecutor::getInstance().sendCallbacks({[&]() {
+ mInvoker->windowInfosChanged({}, {}, false);
+ mInvoker->windowInfosChanged({}, {}, true);
+ }});
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return callCount == expectedCallCount; });
+ }
+ EXPECT_EQ(callCount, expectedCallCount);
+}
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged skips old messages when more than one
+// message is delayed.
+TEST_F(WindowInfosListenerInvokerTest, skipsDelayedMessage) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int64_t lastUpdateId = -1;
+
+ // Simulate a slow ack by not calling the WindowInfosReportedListener.
+ mInvoker->addWindowInfosListener(
+ sp<Listener>::make([&](const gui::WindowInfosUpdate& update,
+ const sp<gui::IWindowInfosReportedListener>&) {
+ std::scoped_lock lock{mutex};
+ lastUpdateId = update.vsyncId;
+ cv.notify_one();
+ }));
+
+ BackgroundExecutor::getInstance().sendCallbacks({[&]() {
+ mInvoker->windowInfosChanged({{}, {}, /* vsyncId= */ 1, 0}, {}, false);
+ mInvoker->windowInfosChanged({{}, {}, /* vsyncId= */ 2, 0}, {}, false);
+ mInvoker->windowInfosChanged({{}, {}, /* vsyncId= */ 3, 0}, {}, false);
+ }});
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return lastUpdateId == 1; });
+ }
+ EXPECT_EQ(lastUpdateId, 1);
+
+ // Ack the first message. The third update should be sent.
+ mInvoker->onWindowInfosReported();
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return lastUpdateId == 3; });
+ }
+ EXPECT_EQ(lastUpdateId, 3);
+}
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged immediately calls listener after a call
+// where no listeners were configured.
+TEST_F(WindowInfosListenerInvokerTest, noListeners) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int callCount = 0;
+
+ // Test that calling windowInfosChanged without any listeners doesn't cause the next call to be
+ // delayed.
+ BackgroundExecutor::getInstance().sendCallbacks({[&]() {
+ mInvoker->windowInfosChanged({}, {}, false);
+ mInvoker->addWindowInfosListener(sp<Listener>::make(
+ [&](const gui::WindowInfosUpdate&, const sp<gui::IWindowInfosReportedListener>&) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ cv.notify_one();
+ }));
+ mInvoker->windowInfosChanged({}, {}, false);
+ }});
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return callCount == 1; });
+ }
+ EXPECT_EQ(callCount, 1);
+}
+
+} // namespace android