Merge "Reduce number of binders from SurfaceFlinger for ADPF" 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 fe0c775..527a477 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -242,6 +242,19 @@
ftl_last = PALM,
};
+/**
+ * The state of the key. This should have 1:1 correspondence with the values of anonymous enum
+ * defined in input.h
+ */
+enum class KeyState {
+ UNKNOWN = AKEY_STATE_UNKNOWN,
+ UP = AKEY_STATE_UP,
+ DOWN = AKEY_STATE_DOWN,
+ VIRTUAL = AKEY_STATE_VIRTUAL,
+ ftl_first = UNKNOWN,
+ ftl_last = VIRTUAL,
+};
+
bool isStylusToolType(ToolType toolType);
/*
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/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index 2106839..c3b2d3d 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -343,14 +343,17 @@
return BAD_VALUE;
}
}
- {
- auto value = getStandardMetadata<StandardMetadataType::USAGE>(mMapper, bufferHandle);
- if (static_cast<BufferUsage>(usage) != value) {
- ALOGW("Usage didn't match, expected %" PRIu64 " got %" PRId64, usage,
- static_cast<int64_t>(value.value_or(BufferUsage::CPU_READ_NEVER)));
- return BAD_VALUE;
- }
- }
+ // TODO: This can false-positive fail if the allocator adjusted the USAGE bits internally
+ // Investigate further & re-enable or remove, but for now ignoring usage should be OK
+ (void)usage;
+ // {
+ // auto value = getStandardMetadata<StandardMetadataType::USAGE>(mMapper, bufferHandle);
+ // if (static_cast<BufferUsage>(usage) != value) {
+ // ALOGW("Usage didn't match, expected %" PRIu64 " got %" PRId64, usage,
+ // static_cast<int64_t>(value.value_or(BufferUsage::CPU_READ_NEVER)));
+ // return BAD_VALUE;
+ // }
+ // }
{
auto value = getStandardMetadata<StandardMetadataType::STRIDE>(mMapper, bufferHandle);
if (stride != value) {
diff --git a/libs/ultrahdr/fuzzer/Android.bp b/libs/ultrahdr/fuzzer/Android.bp
new file mode 100644
index 0000000..27b38c3
--- /dev/null
+++ b/libs/ultrahdr/fuzzer/Android.bp
@@ -0,0 +1,65 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_native_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_defaults {
+ name: "ultrahdr_fuzzer_defaults",
+ host_supported: true,
+ static_libs: ["liblog"],
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ description: "The fuzzers target the APIs of jpeg hdr",
+ service_privilege: "constrained",
+ users: "multi_user",
+ },
+}
+
+cc_fuzz {
+ name: "ultrahdr_enc_fuzzer",
+ defaults: ["ultrahdr_fuzzer_defaults"],
+ srcs: [
+ "ultrahdr_enc_fuzzer.cpp",
+ ],
+ shared_libs: [
+ "libimage_io",
+ "libjpeg",
+ "liblog",
+ ],
+ static_libs: [
+ "libjpegdecoder",
+ "libjpegencoder",
+ "libultrahdr",
+ "libutils",
+ ],
+ fuzz_config: {
+ fuzzed_code_usage: "future_version",
+ vector: "local_no_privileges_required",
+ },
+}
+
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
new file mode 100644
index 0000000..472699b
--- /dev/null
+++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// System include files
+#include <fuzzer/FuzzedDataProvider.h>
+#include <algorithm>
+#include <iostream>
+#include <random>
+#include <vector>
+
+// User include files
+#include "ultrahdr/gainmapmath.h"
+#include "ultrahdr/jpegencoderhelper.h"
+#include "utils/Log.h"
+
+using namespace android::ultrahdr;
+
+// constants
+const int kMinWidth = 8;
+const int kMaxWidth = 7680;
+
+const int kMinHeight = 8;
+const int kMaxHeight = 4320;
+
+const int kScaleFactor = 4;
+
+const int kJpegBlock = 16;
+
+// Color gamuts for image data, sync with ultrahdr.h
+const int kCgMin = ULTRAHDR_COLORGAMUT_UNSPECIFIED + 1;
+const int kCgMax = ULTRAHDR_COLORGAMUT_MAX;
+
+// Transfer functions for image data, sync with ultrahdr.h
+const int kTfMin = ULTRAHDR_TF_UNSPECIFIED + 1;
+const int kTfMax = ULTRAHDR_TF_MAX;
+
+// Transfer functions for image data, sync with ultrahdr.h
+const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1;
+const int kOfMax = ULTRAHDR_OUTPUT_MAX;
+
+// quality factor
+const int kQfMin = 0;
+const int kQfMax = 100;
+
+// seed
+const unsigned kSeed = 0x7ab7;
+
+class JpegHDRFuzzer {
+public:
+ JpegHDRFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
+ void process();
+ void fillP010Buffer(uint16_t* data, int width, int height, int stride);
+ void fill420Buffer(uint8_t* data, int size);
+
+private:
+ FuzzedDataProvider mFdp;
+};
+
+void JpegHDRFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) {
+ uint16_t* tmp = data;
+ std::vector<uint16_t> buffer(16);
+ for (int i = 0; i < buffer.size(); i++) {
+ buffer[i] = mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1);
+ }
+ for (int j = 0; j < height; j++) {
+ for (int i = 0; i < width; i += buffer.size()) {
+ memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (width - i)));
+ std::shuffle(buffer.begin(), buffer.end(), std::default_random_engine(kSeed));
+ }
+ tmp += stride;
+ }
+}
+
+void JpegHDRFuzzer::fill420Buffer(uint8_t* data, int size) {
+ std::vector<uint8_t> buffer(16);
+ mFdp.ConsumeData(buffer.data(), buffer.size());
+ for (int i = 0; i < size; i += buffer.size()) {
+ memcpy(data + i, buffer.data(), std::min((int)buffer.size(), (size - i)));
+ std::shuffle(buffer.begin(), buffer.end(), std::default_random_engine(kSeed));
+ }
+}
+
+void JpegHDRFuzzer::process() {
+ while (mFdp.remaining_bytes()) {
+ struct jpegr_uncompressed_struct p010Img {};
+ struct jpegr_uncompressed_struct yuv420Img {};
+ struct jpegr_uncompressed_struct grayImg {};
+ struct jpegr_compressed_struct jpegImgR {};
+ struct jpegr_compressed_struct jpegImg {};
+ struct jpegr_compressed_struct jpegGainMap {};
+
+ // which encode api to select
+ int muxSwitch = mFdp.ConsumeIntegralInRange<int>(0, 4);
+
+ // quality factor
+ int quality = mFdp.ConsumeIntegralInRange<int>(kQfMin, kQfMax);
+
+ // hdr_tf
+ auto tf = static_cast<ultrahdr_transfer_function>(
+ mFdp.ConsumeIntegralInRange<int>(kTfMin, kTfMax));
+
+ // p010 Cg
+ auto p010Cg =
+ static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax));
+
+ // 420 Cg
+ auto yuv420Cg =
+ static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax));
+
+ // hdr_of
+ auto of = static_cast<ultrahdr_output_format>(
+ mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax));
+
+ int width = mFdp.ConsumeIntegralInRange<int>(kMinWidth, kMaxWidth);
+ width = (width >> 1) << 1;
+
+ int height = mFdp.ConsumeIntegralInRange<int>(kMinHeight, kMaxHeight);
+ height = (height >> 1) << 1;
+
+ std::unique_ptr<uint16_t[]> bufferY = nullptr;
+ std::unique_ptr<uint16_t[]> bufferUV = nullptr;
+ std::unique_ptr<uint8_t[]> yuv420ImgRaw = nullptr;
+ std::unique_ptr<uint8_t[]> grayImgRaw = nullptr;
+ if (muxSwitch != 4) {
+ // init p010 image
+ bool isUVContiguous = mFdp.ConsumeBool();
+ bool hasYStride = mFdp.ConsumeBool();
+ int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
+ p010Img.width = width;
+ p010Img.height = height;
+ p010Img.colorGamut = p010Cg;
+ p010Img.luma_stride = hasYStride ? yStride : 0;
+ int bppP010 = 2;
+ if (isUVContiguous) {
+ size_t p010Size = yStride * height * 3 / 2;
+ bufferY = std::make_unique<uint16_t[]>(p010Size);
+ p010Img.data = bufferY.get();
+ p010Img.chroma_data = nullptr;
+ p010Img.chroma_stride = 0;
+ fillP010Buffer(bufferY.get(), width, height, yStride);
+ fillP010Buffer(bufferY.get() + yStride * height, width, height / 2, yStride);
+ } else {
+ int uvStride = mFdp.ConsumeIntegralInRange<int>(width, width + 128);
+ size_t p010YSize = yStride * height;
+ bufferY = std::make_unique<uint16_t[]>(p010YSize);
+ p010Img.data = bufferY.get();
+ fillP010Buffer(bufferY.get(), width, height, yStride);
+ size_t p010UVSize = uvStride * p010Img.height / 2;
+ bufferUV = std::make_unique<uint16_t[]>(p010UVSize);
+ p010Img.chroma_data = bufferUV.get();
+ p010Img.chroma_stride = uvStride;
+ fillP010Buffer(bufferUV.get(), width, height / 2, uvStride);
+ }
+ } else {
+ int map_width = width / kScaleFactor;
+ int map_height = height / kScaleFactor;
+ map_width = static_cast<size_t>(floor((map_width + kJpegBlock - 1) / kJpegBlock)) *
+ kJpegBlock;
+ map_height = ((map_height + 1) >> 1) << 1;
+ // init 400 image
+ grayImg.width = map_width;
+ grayImg.height = map_height;
+ grayImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+
+ const size_t graySize = map_width * map_height;
+ grayImgRaw = std::make_unique<uint8_t[]>(graySize);
+ grayImg.data = grayImgRaw.get();
+ fill420Buffer(grayImgRaw.get(), graySize);
+ grayImg.chroma_data = nullptr;
+ grayImg.luma_stride = 0;
+ grayImg.chroma_stride = 0;
+ }
+
+ if (muxSwitch > 0) {
+ // init 420 image
+ yuv420Img.width = width;
+ yuv420Img.height = height;
+ yuv420Img.colorGamut = yuv420Cg;
+
+ const size_t yuv420Size = (yuv420Img.width * yuv420Img.height * 3) / 2;
+ yuv420ImgRaw = std::make_unique<uint8_t[]>(yuv420Size);
+ yuv420Img.data = yuv420ImgRaw.get();
+ fill420Buffer(yuv420ImgRaw.get(), yuv420Size);
+ yuv420Img.chroma_data = nullptr;
+ yuv420Img.luma_stride = 0;
+ yuv420Img.chroma_stride = 0;
+ }
+
+ // dest
+ // 2 * p010 size as input data is random, DCT compression might not behave as expected
+ jpegImgR.maxLength = std::max(8 * 1024 /* min size 8kb */, width * height * 3 * 2);
+ auto jpegImgRaw = std::make_unique<uint8_t[]>(jpegImgR.maxLength);
+ jpegImgR.data = jpegImgRaw.get();
+
+//#define DUMP_PARAM
+#ifdef DUMP_PARAM
+ std::cout << "Api Select " << muxSwitch << std::endl;
+ std::cout << "image dimensions " << width << " x " << height << std::endl;
+ std::cout << "p010 color gamut " << p010Img.colorGamut << std::endl;
+ std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl;
+ std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl;
+ std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl;
+ std::cout << "quality factor " << quality << std::endl;
+#endif
+
+ JpegR jpegHdr;
+ android::status_t status = android::UNKNOWN_ERROR;
+ if (muxSwitch == 0) { // api 0
+ jpegImgR.length = 0;
+ status = jpegHdr.encodeJPEGR(&p010Img, tf, &jpegImgR, quality, nullptr);
+ } else if (muxSwitch == 1) { // api 1
+ jpegImgR.length = 0;
+ status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, tf, &jpegImgR, quality, nullptr);
+ } else {
+ // compressed img
+ JpegEncoderHelper encoder;
+ if (encoder.compressImage(yuv420Img.data, yuv420Img.width, yuv420Img.height, quality,
+ nullptr, 0)) {
+ jpegImg.length = encoder.getCompressedImageSize();
+ jpegImg.maxLength = jpegImg.length;
+ jpegImg.data = encoder.getCompressedImagePtr();
+ jpegImg.colorGamut = yuv420Cg;
+
+ if (muxSwitch == 2) { // api 2
+ jpegImgR.length = 0;
+ status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, &jpegImg, tf, &jpegImgR);
+ } else if (muxSwitch == 3) { // api 3
+ jpegImgR.length = 0;
+ status = jpegHdr.encodeJPEGR(&p010Img, &jpegImg, tf, &jpegImgR);
+ } else if (muxSwitch == 4) { // api 4
+ jpegImgR.length = 0;
+ JpegEncoderHelper gainMapEncoder;
+ if (gainMapEncoder.compressImage(grayImg.data, grayImg.width, grayImg.height,
+ quality, nullptr, 0, true)) {
+ jpegGainMap.length = gainMapEncoder.getCompressedImageSize();
+ jpegGainMap.maxLength = jpegImg.length;
+ jpegGainMap.data = gainMapEncoder.getCompressedImagePtr();
+ jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ultrahdr_metadata_struct metadata;
+ metadata.version = "1.3.1";
+ if (tf == ULTRAHDR_TF_HLG) {
+ metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits;
+ } else if (tf == ULTRAHDR_TF_PQ) {
+ metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits;
+ } else {
+ metadata.maxContentBoost = 0;
+ }
+ metadata.minContentBoost = 1.0f;
+ status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR);
+ }
+ }
+ }
+ }
+ if (status == android::OK) {
+ jpegr_uncompressed_struct decodedJpegR;
+ auto decodedRaw = std::make_unique<uint8_t[]>(width * height * 8);
+ decodedJpegR.data = decodedRaw.get();
+ jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
+ mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX), nullptr, of,
+ nullptr, nullptr);
+ std::vector<uint8_t> iccData(0);
+ std::vector<uint8_t> exifData(0);
+ jpegr_info_struct info{0, 0, &iccData, &exifData};
+ jpegHdr.getJPEGRInfo(&jpegImgR, &info);
+ }
+ }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ JpegHDRFuzzer fuzzHandle(data, size);
+ fuzzHandle.process();
+ return 0;
+}
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
index 88038f1..1f9bd0f 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegr.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegr.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_ULTRAHDR_JPEGR_H
#define ANDROID_ULTRAHDR_JPEGR_H
+#include "jpegencoderhelper.h"
#include "jpegrerrorcode.h"
#include "ultrahdr.h"
@@ -312,11 +313,11 @@
* This method is called in the encoding pipeline. It will encode the gain map.
*
* @param uncompressed_gain_map uncompressed gain map
- * @param dest encoded recover map
+ * @param resource to compress gain map
* @return NO_ERROR if encoding succeeds, error code if error occurs.
*/
status_t compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
- jr_compressed_ptr dest);
+ JpegEncoderHelper* jpeg_encoder);
/*
* This methoud is called to separate primary image and gain map image from JPEGR
@@ -373,14 +374,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 cb8197c..c250aa0 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;
@@ -169,11 +244,13 @@
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+ JpegEncoderHelper jpeg_encoder_gainmap;
+ JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
jpegr_compressed_struct compressed_map;
- compressed_map.maxLength = map.width * map.height;
- unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
- compressed_map.data = compressed_map_data.get();
- JPEGR_CHECK(compressGainMap(&map, &compressed_map));
+ compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
+ compressed_map.length = compressed_map.maxLength;
+ compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
+ compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
uncompressed_yuv_420_image.colorGamut);
@@ -201,18 +278,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;
}
@@ -225,11 +303,13 @@
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+ JpegEncoderHelper jpeg_encoder_gainmap;
+ JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
jpegr_compressed_struct compressed_map;
- compressed_map.maxLength = map.width * map.height;
- unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
- compressed_map.data = compressed_map_data.get();
- JPEGR_CHECK(compressGainMap(&map, &compressed_map));
+ compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
+ compressed_map.length = compressed_map.maxLength;
+ compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
+ compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
uncompressed_yuv_420_image->colorGamut);
@@ -256,15 +336,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;
}
@@ -277,11 +360,13 @@
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+ JpegEncoderHelper jpeg_encoder_gainmap;
+ JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
jpegr_compressed_struct compressed_map;
- compressed_map.maxLength = map.width * map.height;
- unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
- compressed_map.data = compressed_map_data.get();
- JPEGR_CHECK(compressGainMap(&map, &compressed_map));
+ compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
+ compressed_map.length = compressed_map.maxLength;
+ compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
+ compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
@@ -293,14 +378,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;
}
@@ -328,11 +413,13 @@
std::unique_ptr<uint8_t[]> map_data;
map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+ JpegEncoderHelper jpeg_encoder_gainmap;
+ JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
jpegr_compressed_struct compressed_map;
- compressed_map.maxLength = map.width * map.height;
- unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
- compressed_map.data = compressed_map_data.get();
- JPEGR_CHECK(compressGainMap(&map, &compressed_map));
+ compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
+ compressed_map.length = compressed_map.maxLength;
+ compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
+ compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
@@ -344,13 +431,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 +484,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) {
@@ -482,30 +612,21 @@
}
status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
- jr_compressed_ptr dest) {
- if (uncompressed_gain_map == nullptr || dest == nullptr) {
+ JpegEncoderHelper* jpeg_encoder) {
+ if (uncompressed_gain_map == nullptr || jpeg_encoder == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- JpegEncoderHelper jpeg_encoder;
- if (!jpeg_encoder.compressImage(uncompressed_gain_map->data,
- uncompressed_gain_map->width,
- uncompressed_gain_map->height,
- kMapCompressQuality,
- nullptr,
- 0,
- true /* isSingleChannel */)) {
+ if (!jpeg_encoder->compressImage(uncompressed_gain_map->data,
+ uncompressed_gain_map->width,
+ uncompressed_gain_map->height,
+ kMapCompressQuality,
+ nullptr,
+ 0,
+ true /* isSingleChannel */)) {
return ERROR_JPEGR_ENCODE_ERROR;
}
- if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
- return ERROR_JPEGR_BUFFER_TOO_SMALL;
- }
-
- memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
- dest->length = jpeg_encoder.getCompressedImageSize();
- dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-
return NO_ERROR;
}
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 9125fe4..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);
}
}
@@ -5651,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) {
@@ -6700,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 0e9cfef..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.
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index c8c5115..0a64a1c 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -482,8 +482,8 @@
}
if (keyboardSource != 0) {
- mappers.push_back(std::make_unique<KeyboardInputMapper>(contextPtr, readerConfig,
- keyboardSource, keyboardType));
+ mappers.push_back(createInputMapper<KeyboardInputMapper>(contextPtr, readerConfig,
+ keyboardSource, keyboardType));
}
// Cursor-like devices.
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/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 52277ff..569690a 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -40,6 +40,7 @@
"AnrTracker_test.cpp",
"BlockingQueue_test.cpp",
"CapturedTouchpadEventConverter_test.cpp",
+ "CursorInputMapper_test.cpp",
"EventHub_test.cpp",
"FakeEventHub.cpp",
"FakeInputReaderPolicy.cpp",
@@ -58,6 +59,7 @@
"PreferStylusOverTouch_test.cpp",
"PropertyProvider_test.cpp",
"TestInputListener.cpp",
+ "TouchpadInputMapper_test.cpp",
"UinputDevice.cpp",
"UnwantedInteractionBlocker_test.cpp",
],
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
new file mode 100644
index 0000000..6774b17
--- /dev/null
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CursorInputMapper.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+#include "FakePointerController.h"
+#include "InputMapperTest.h"
+#include "InterfaceMocks.h"
+#include "TestInputListenerMatchers.h"
+
+#define TAG "CursorInputMapper_test"
+
+namespace android {
+
+using testing::Return;
+using testing::VariantWith;
+constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr auto ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+constexpr auto ACTION_UP = AMOTION_EVENT_ACTION_UP;
+constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS;
+constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
+constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
+
+/**
+ * Unit tests for CursorInputMapper.
+ * This class is named 'CursorInputMapperUnitTest' to avoid name collision with the existing
+ * 'CursorInputMapperTest'. If all of the CursorInputMapper tests are migrated here, the name
+ * can be simplified to 'CursorInputMapperTest'.
+ * TODO(b/283812079): move CursorInputMapper tests here.
+ */
+class CursorInputMapperUnitTest : public InputMapperUnitTest {
+protected:
+ void SetUp() override {
+ InputMapperUnitTest::SetUp();
+
+ // Current scan code state - all keys are UP by default
+ setScanCodeState(KeyState::UP,
+ {BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, BTN_BACK, BTN_SIDE, BTN_FORWARD,
+ BTN_EXTRA, BTN_TASK});
+ EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
+ .WillRepeatedly(Return(false));
+
+ EXPECT_CALL(mMockInputReaderContext, bumpGeneration()).WillRepeatedly(Return(1));
+
+ mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
+ }
+};
+
+/**
+ * Move the mouse and then click the button. Check whether HOVER_EXIT is generated when hovering
+ * ends. Currently, it is not.
+ */
+TEST_F(CursorInputMapperUnitTest, HoverAndLeftButtonPress) {
+ std::list<NotifyArgs> args;
+
+ // Move the cursor a little
+ args += process(EV_REL, REL_X, 10);
+ args += process(EV_REL, REL_Y, 20);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+
+ // Now click the mouse button
+ args.clear();
+ args += process(EV_KEY, BTN_LEFT, 1);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS))));
+
+ // Move some more.
+ args.clear();
+ args += process(EV_REL, REL_X, 10);
+ args += process(EV_REL, REL_Y, 20);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args, ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_MOVE))));
+
+ // Release the button
+ args.clear();
+ args += process(EV_KEY, BTN_LEFT, 0);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+}
+
+} // 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/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index ad48a79..0eee2b9 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -22,6 +22,74 @@
namespace android {
+using testing::Return;
+
+void InputMapperUnitTest::SetUp() {
+ mFakePointerController = std::make_shared<FakePointerController>();
+ mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+ mFakePointerController->setPosition(400, 240);
+
+ EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID))
+ .WillRepeatedly(Return(mFakePointerController));
+
+ EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub));
+ InputDeviceIdentifier identifier;
+ identifier.name = "device";
+ identifier.location = "USB1";
+ identifier.bus = 0;
+
+ EXPECT_CALL(mMockEventHub, getDeviceIdentifier(EVENTHUB_ID)).WillRepeatedly(Return(identifier));
+ mDevice = std::make_unique<InputDevice>(&mMockInputReaderContext, DEVICE_ID,
+ /*generation=*/2, identifier);
+ mDeviceContext = std::make_unique<InputDeviceContext>(*mDevice, EVENTHUB_ID);
+}
+
+void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max,
+ int32_t resolution) {
+ EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
+ .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
+ outAxisInfo->valid = valid;
+ outAxisInfo->minValue = min;
+ outAxisInfo->maxValue = max;
+ outAxisInfo->flat = 0;
+ outAxisInfo->fuzz = 0;
+ outAxisInfo->resolution = resolution;
+ return valid ? OK : -1;
+ });
+}
+
+void InputMapperUnitTest::expectScanCodes(bool present, std::set<int> scanCodes) {
+ for (const auto& scanCode : scanCodes) {
+ EXPECT_CALL(mMockEventHub, hasScanCode(EVENTHUB_ID, scanCode))
+ .WillRepeatedly(testing::Return(present));
+ }
+}
+
+void InputMapperUnitTest::setScanCodeState(KeyState state, std::set<int> scanCodes) {
+ for (const auto& scanCode : scanCodes) {
+ EXPECT_CALL(mMockEventHub, getScanCodeState(EVENTHUB_ID, scanCode))
+ .WillRepeatedly(testing::Return(static_cast<int>(state)));
+ }
+}
+
+void InputMapperUnitTest::setKeyCodeState(KeyState state, std::set<int> keyCodes) {
+ for (const auto& keyCode : keyCodes) {
+ EXPECT_CALL(mMockEventHub, getKeyCodeState(EVENTHUB_ID, keyCode))
+ .WillRepeatedly(testing::Return(static_cast<int>(state)));
+ }
+}
+
+std::list<NotifyArgs> InputMapperUnitTest::process(int32_t type, int32_t code, int32_t value) {
+ RawEvent event;
+ event.when = systemTime(SYSTEM_TIME_MONOTONIC);
+ event.readTime = event.when;
+ event.deviceId = mMapper->getDeviceContext().getEventHubId();
+ event.type = type;
+ event.code = code;
+ event.value = value;
+ return mMapper->process(&event);
+}
+
const char* InputMapperTest::DEVICE_NAME = "device";
const char* InputMapperTest::DEVICE_LOCATION = "USB1";
const ftl::Flags<InputDeviceClass> InputMapperTest::DEVICE_CLASSES =
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index 2b6655c..909bd9c 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -23,16 +23,48 @@
#include <InputMapper.h>
#include <NotifyArgs.h>
#include <ftl/flags.h>
+#include <gmock/gmock.h>
#include <utils/StrongPointer.h>
#include "FakeEventHub.h"
#include "FakeInputReaderPolicy.h"
#include "InstrumentedInputReader.h"
+#include "InterfaceMocks.h"
#include "TestConstants.h"
#include "TestInputListener.h"
namespace android {
+class InputMapperUnitTest : public testing::Test {
+protected:
+ static constexpr int32_t EVENTHUB_ID = 1;
+ static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+ virtual void SetUp() override;
+
+ void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution);
+
+ void expectScanCodes(bool present, std::set<int> scanCodes);
+
+ void setScanCodeState(KeyState state, std::set<int> scanCodes);
+
+ void setKeyCodeState(KeyState state, std::set<int> keyCodes);
+
+ std::list<NotifyArgs> process(int32_t type, int32_t code, int32_t value);
+
+ MockEventHubInterface mMockEventHub;
+ std::shared_ptr<FakePointerController> mFakePointerController;
+ MockInputReaderContext mMockInputReaderContext;
+ std::unique_ptr<InputDevice> mDevice;
+
+ std::unique_ptr<InputDeviceContext> mDeviceContext;
+ InputReaderConfiguration mReaderConfiguration;
+ // The mapper should be created by the subclasses.
+ std::unique_ptr<InputMapper> mMapper;
+};
+
+/**
+ * Deprecated - use InputMapperUnitTest instead.
+ */
class InputMapperTest : public testing::Test {
protected:
static const char* DEVICE_NAME;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 9fbe762..bfb371f 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -2892,7 +2892,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 +2908,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 +3009,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 +3031,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 +3054,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 +3095,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 +3117,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 +3189,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 +3215,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 +3243,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 +3255,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 +3268,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 +3280,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 +3300,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 +3366,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 +3416,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 +3487,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 +3533,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 +3558,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 +3580,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 +3598,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 +3660,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 +3686,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 +3718,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 +3744,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 +3782,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/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
new file mode 100644
index 0000000..d720a90
--- /dev/null
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/logging.h>
+#include <gmock/gmock.h>
+
+namespace android {
+
+class MockInputReaderContext : public InputReaderContext {
+public:
+ MOCK_METHOD(void, updateGlobalMetaState, (), (override));
+ int32_t getGlobalMetaState() override { return 0; };
+
+ MOCK_METHOD(void, disableVirtualKeysUntil, (nsecs_t time), (override));
+ MOCK_METHOD(bool, shouldDropVirtualKey, (nsecs_t now, int32_t keyCode, int32_t scanCode),
+ (override));
+
+ MOCK_METHOD(void, fadePointer, (), (override));
+ MOCK_METHOD(std::shared_ptr<PointerControllerInterface>, getPointerController,
+ (int32_t deviceId), (override));
+
+ MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override));
+ MOCK_METHOD(int32_t, bumpGeneration, (), (override));
+
+ MOCK_METHOD(void, getExternalStylusDevices, (std::vector<InputDeviceInfo> & outDevices),
+ (override));
+ MOCK_METHOD(std::list<NotifyArgs>, dispatchExternalStylusState, (const StylusState& outState),
+ (override));
+
+ MOCK_METHOD(InputReaderPolicyInterface*, getPolicy, (), (override));
+ MOCK_METHOD(EventHubInterface*, getEventHub, (), (override));
+
+ int32_t getNextId() override { return 1; };
+
+ MOCK_METHOD(void, updateLedMetaState, (int32_t metaState), (override));
+ MOCK_METHOD(int32_t, getLedMetaState, (), (override));
+};
+
+class MockEventHubInterface : public EventHubInterface {
+public:
+ MOCK_METHOD(ftl::Flags<InputDeviceClass>, getDeviceClasses, (int32_t deviceId), (const));
+ MOCK_METHOD(InputDeviceIdentifier, getDeviceIdentifier, (int32_t deviceId), (const));
+ MOCK_METHOD(int32_t, getDeviceControllerNumber, (int32_t deviceId), (const));
+ MOCK_METHOD(std::optional<PropertyMap>, getConfiguration, (int32_t deviceId), (const));
+ MOCK_METHOD(status_t, getAbsoluteAxisInfo,
+ (int32_t deviceId, int axis, RawAbsoluteAxisInfo* outAxisInfo), (const));
+ MOCK_METHOD(bool, hasRelativeAxis, (int32_t deviceId, int axis), (const));
+ MOCK_METHOD(bool, hasInputProperty, (int32_t deviceId, int property), (const));
+ MOCK_METHOD(bool, hasMscEvent, (int32_t deviceId, int mscEvent), (const));
+ MOCK_METHOD(void, addKeyRemapping, (int32_t deviceId, int fromKeyCode, int toKeyCode), (const));
+ MOCK_METHOD(status_t, mapKey,
+ (int32_t deviceId, int scanCode, int usageCode, int32_t metaState,
+ int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags),
+ (const));
+ MOCK_METHOD(status_t, mapAxis, (int32_t deviceId, int scanCode, AxisInfo* outAxisInfo),
+ (const));
+ MOCK_METHOD(void, setExcludedDevices, (const std::vector<std::string>& devices));
+ MOCK_METHOD(std::vector<RawEvent>, getEvents, (int timeoutMillis));
+ MOCK_METHOD(std::vector<TouchVideoFrame>, getVideoFrames, (int32_t deviceId));
+ MOCK_METHOD((base::Result<std::pair<InputDeviceSensorType, int32_t>>), mapSensor,
+ (int32_t deviceId, int32_t absCode), (const, override));
+ MOCK_METHOD(std::vector<int32_t>, getRawBatteryIds, (int32_t deviceId), (const, override));
+ MOCK_METHOD(std::optional<RawBatteryInfo>, getRawBatteryInfo,
+ (int32_t deviceId, int32_t BatteryId), (const, override));
+ MOCK_METHOD(std::vector<int32_t>, getRawLightIds, (int32_t deviceId), (const, override));
+ MOCK_METHOD(std::optional<RawLightInfo>, getRawLightInfo, (int32_t deviceId, int32_t lightId),
+ (const, override));
+ MOCK_METHOD(std::optional<int32_t>, getLightBrightness, (int32_t deviceId, int32_t lightId),
+ (const, override));
+ MOCK_METHOD(void, setLightBrightness, (int32_t deviceId, int32_t lightId, int32_t brightness),
+ (override));
+ MOCK_METHOD((std::optional<std::unordered_map<LightColor, int32_t>>), getLightIntensities,
+ (int32_t deviceId, int32_t lightId), (const, override));
+ MOCK_METHOD(void, setLightIntensities,
+ (int32_t deviceId, int32_t lightId,
+ (std::unordered_map<LightColor, int32_t>)intensities),
+ (override));
+
+ MOCK_METHOD(std::optional<RawLayoutInfo>, getRawLayoutInfo, (int32_t deviceId),
+ (const, override));
+ MOCK_METHOD(int32_t, getScanCodeState, (int32_t deviceId, int32_t scanCode), (const, override));
+ MOCK_METHOD(int32_t, getKeyCodeState, (int32_t deviceId, int32_t keyCode), (const, override));
+ MOCK_METHOD(int32_t, getSwitchState, (int32_t deviceId, int32_t sw), (const, override));
+
+ MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue),
+ (const, override));
+ MOCK_METHOD(int32_t, getKeyCodeForKeyLocation, (int32_t deviceId, int32_t locationKeyCode),
+ (const, override));
+ MOCK_METHOD(bool, markSupportedKeyCodes,
+ (int32_t deviceId, const std::vector<int32_t>& keyCodes, uint8_t* outFlags),
+ (const, override));
+
+ MOCK_METHOD(bool, hasScanCode, (int32_t deviceId, int32_t scanCode), (const, override));
+
+ MOCK_METHOD(bool, hasKeyCode, (int32_t deviceId, int32_t keyCode), (const, override));
+
+ MOCK_METHOD(bool, hasLed, (int32_t deviceId, int32_t led), (const, override));
+
+ MOCK_METHOD(void, setLedState, (int32_t deviceId, int32_t led, bool on), (override));
+
+ MOCK_METHOD(void, getVirtualKeyDefinitions,
+ (int32_t deviceId, std::vector<VirtualKeyDefinition>& outVirtualKeys),
+ (const, override));
+
+ MOCK_METHOD(const std::shared_ptr<KeyCharacterMap>, getKeyCharacterMap, (int32_t deviceId),
+ (const, override));
+
+ MOCK_METHOD(bool, setKeyboardLayoutOverlay,
+ (int32_t deviceId, std::shared_ptr<KeyCharacterMap> map), (override));
+
+ MOCK_METHOD(void, vibrate, (int32_t deviceId, const VibrationElement& effect), (override));
+ MOCK_METHOD(void, cancelVibrate, (int32_t deviceId), (override));
+
+ MOCK_METHOD(std::vector<int32_t>, getVibratorIds, (int32_t deviceId), (const, override));
+ MOCK_METHOD(std::optional<int32_t>, getBatteryCapacity, (int32_t deviceId, int32_t batteryId),
+ (const, override));
+
+ MOCK_METHOD(std::optional<int32_t>, getBatteryStatus, (int32_t deviceId, int32_t batteryId),
+ (const, override));
+ MOCK_METHOD(void, requestReopenDevices, (), (override));
+ MOCK_METHOD(void, wake, (), (override));
+
+ MOCK_METHOD(void, dump, (std::string & dump), (const, override));
+ MOCK_METHOD(void, monitor, (), (const, override));
+ MOCK_METHOD(bool, isDeviceEnabled, (int32_t deviceId), (const, override));
+ MOCK_METHOD(status_t, enableDevice, (int32_t deviceId), (override));
+ MOCK_METHOD(status_t, disableDevice, (int32_t deviceId), (override));
+ MOCK_METHOD(void, sysfsNodeChanged, (const std::string& sysfsNodePath), (override));
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
new file mode 100644
index 0000000..92cd462
--- /dev/null
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TouchpadInputMapper.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+#include <thread>
+#include "FakePointerController.h"
+#include "InputMapperTest.h"
+#include "InterfaceMocks.h"
+#include "TestInputListenerMatchers.h"
+
+#define TAG "TouchpadInputMapper_test"
+
+namespace android {
+
+using testing::Return;
+using testing::VariantWith;
+constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr auto ACTION_UP = AMOTION_EVENT_ACTION_UP;
+constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS;
+constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
+constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
+
+/**
+ * Unit tests for TouchpadInputMapper.
+ */
+class TouchpadInputMapperTest : public InputMapperUnitTest {
+protected:
+ void SetUp() override {
+ InputMapperUnitTest::SetUp();
+
+ // Present scan codes: BTN_TOUCH and BTN_TOOL_FINGER
+ expectScanCodes(/*present=*/true,
+ {BTN_LEFT, BTN_RIGHT, BTN_TOOL_FINGER, BTN_TOOL_QUINTTAP, BTN_TOUCH,
+ BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP});
+ // Missing scan codes that the mapper checks for.
+ expectScanCodes(/*present=*/false,
+ {BTN_TOOL_PEN, BTN_TOOL_RUBBER, BTN_TOOL_BRUSH, BTN_TOOL_PENCIL,
+ BTN_TOOL_AIRBRUSH});
+
+ // Current scan code state - all keys are UP by default
+ setScanCodeState(KeyState::UP, {BTN_TOUCH, BTN_STYLUS,
+ BTN_STYLUS2, BTN_0,
+ BTN_TOOL_FINGER, BTN_TOOL_PEN,
+ BTN_TOOL_RUBBER, BTN_TOOL_BRUSH,
+ BTN_TOOL_PENCIL, BTN_TOOL_AIRBRUSH,
+ BTN_TOOL_MOUSE, BTN_TOOL_LENS,
+ BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP,
+ BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP,
+ BTN_LEFT, BTN_RIGHT,
+ BTN_MIDDLE, BTN_BACK,
+ BTN_SIDE, BTN_FORWARD,
+ BTN_EXTRA, BTN_TASK});
+
+ setKeyCodeState(KeyState::UP,
+ {AKEYCODE_STYLUS_BUTTON_PRIMARY, AKEYCODE_STYLUS_BUTTON_SECONDARY});
+
+ // Key mappings
+ EXPECT_CALL(mMockEventHub,
+ mapKey(EVENTHUB_ID, BTN_LEFT, /*usageCode=*/0, /*metaState=*/0, testing::_,
+ testing::_, testing::_))
+ .WillRepeatedly(Return(NAME_NOT_FOUND));
+
+ // Input properties - only INPUT_PROP_BUTTONPAD
+ EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_BUTTONPAD))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, INPUT_PROP_SEMI_MT))
+ .WillRepeatedly(Return(false));
+
+ // Axes that the device has
+ setupAxis(ABS_MT_SLOT, /*valid=*/true, /*min=*/0, /*max=*/4, /*resolution=*/0);
+ setupAxis(ABS_MT_POSITION_X, /*valid=*/true, /*min=*/0, /*max=*/2000, /*resolution=*/24);
+ setupAxis(ABS_MT_POSITION_Y, /*valid=*/true, /*min=*/0, /*max=*/1000, /*resolution=*/24);
+ setupAxis(ABS_MT_PRESSURE, /*valid=*/true, /*min*/ 0, /*max=*/255, /*resolution=*/0);
+ // Axes that the device does not have
+ setupAxis(ABS_MT_ORIENTATION, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+ setupAxis(ABS_MT_TOUCH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+ setupAxis(ABS_MT_TOUCH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+ setupAxis(ABS_MT_WIDTH_MAJOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+ setupAxis(ABS_MT_WIDTH_MINOR, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
+
+ EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_))
+ .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) {
+ *outValue = 0;
+ return OK;
+ });
+ mMapper = createInputMapper<TouchpadInputMapper>(*mDeviceContext, mReaderConfiguration);
+ }
+};
+
+/**
+ * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is
+ * generated when hovering stops. Currently, it is not.
+ * In the current implementation, HOVER_MOVE and ACTION_DOWN events are not sent out right away,
+ * but only after the button is released.
+ */
+TEST_F(TouchpadInputMapperTest, HoverAndLeftButtonPress) {
+ std::list<NotifyArgs> args;
+
+ args += process(EV_ABS, ABS_MT_TRACKING_ID, 1);
+ args += process(EV_KEY, BTN_TOUCH, 1);
+ setScanCodeState(KeyState::DOWN, {BTN_TOOL_FINGER});
+ args += process(EV_KEY, BTN_TOOL_FINGER, 1);
+ args += process(EV_ABS, ABS_MT_POSITION_X, 50);
+ args += process(EV_ABS, ABS_MT_POSITION_Y, 50);
+ args += process(EV_ABS, ABS_MT_PRESSURE, 1);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args, testing::IsEmpty());
+
+ // Without this sleep, the test fails.
+ // TODO(b/284133337): Figure out whether this can be removed
+ std::this_thread::sleep_for(std::chrono::milliseconds(20));
+
+ args += process(EV_KEY, BTN_LEFT, 1);
+ setScanCodeState(KeyState::DOWN, {BTN_LEFT});
+ args += process(EV_SYN, SYN_REPORT, 0);
+
+ args += process(EV_KEY, BTN_LEFT, 0);
+ setScanCodeState(KeyState::UP, {BTN_LEFT});
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP))));
+
+ // Liftoff
+ args.clear();
+ args += process(EV_ABS, ABS_MT_PRESSURE, 0);
+ args += process(EV_ABS, ABS_MT_TRACKING_ID, -1);
+ args += process(EV_KEY, BTN_TOUCH, 0);
+ setScanCodeState(KeyState::UP, {BTN_TOOL_FINGER});
+ args += process(EV_KEY, BTN_TOOL_FINGER, 0);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args, testing::IsEmpty());
+}
+
+} // namespace android
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/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 bbfed8a..79378be 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -2550,7 +2550,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.
@@ -2677,12 +2677,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();
}
@@ -3740,7 +3743,7 @@
doCommitTransactions();
}
-void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId) {
+void SurfaceFlinger::updateInputFlinger(VsyncId vsyncId, TimePoint frameTime) {
if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) {
return;
}
@@ -3752,8 +3755,6 @@
if (mUpdateInputInfo) {
mUpdateInputInfo = false;
updateWindowInfo = true;
- mLastInputFlingerUpdateVsyncId = vsyncId;
- mLastInputFlingerUpdateTimestamp = systemTime();
buildWindowInfos(windowInfos, displayInfos);
}
@@ -3775,17 +3776,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.
@@ -6152,27 +6153,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) {
@@ -7049,9 +7037,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