summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/battery/LongArrayMultiStateCounter.cpp147
-rw-r--r--libs/battery/LongArrayMultiStateCounter.h61
-rw-r--r--libs/battery/LongArrayMultiStateCounterTest.cpp28
-rw-r--r--libs/battery/MultiStateCounter.h91
-rw-r--r--libs/battery/MultiStateCounterTest.cpp7
-rw-r--r--libs/binder/Android.bp1
-rw-r--r--libs/binder/IActivityManager.cpp16
-rw-r--r--libs/binder/IPCThreadState.cpp31
-rw-r--r--libs/binder/Parcel.cpp222
-rw-r--r--libs/binder/PersistableBundle.cpp5
-rw-r--r--libs/binder/include/binder/IInterface.h1
-rw-r--r--libs/binder/include/binder/IPCThreadState.h5
-rw-r--r--libs/binder/include/binder/Parcel.h20
-rw-r--r--libs/binder/include/binder/SafeInterface.h2
-rw-r--r--libs/binder/ndk/include_ndk/android/binder_ibinder.h3
-rw-r--r--libs/binder/ndk/include_platform/android/binder_manager.h4
-rw-r--r--libs/binder/ndk/service_manager.cpp3
-rw-r--r--libs/binder/rust/src/state.rs5
-rw-r--r--libs/binder/tests/binderLibTest.cpp155
-rw-r--r--libs/binder/tests/binderParcelUnitTest.cpp32
-rw-r--r--libs/binder/tests/parcel_fuzzer/binder.cpp123
-rw-r--r--libs/binder/tests/parcel_fuzzer/binder.h1
-rw-r--r--libs/binder/tests/parcel_fuzzer/binder_ndk.cpp54
-rw-r--r--libs/binder/tests/parcel_fuzzer/binder_ndk.h1
-rw-r--r--libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h3
-rw-r--r--libs/binder/tests/parcel_fuzzer/main.cpp55
-rw-r--r--libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h4
-rw-r--r--libs/binder/tests/parcel_fuzzer/random_parcel.cpp34
-rw-r--r--libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp6
-rw-r--r--libs/debugstore/rust/src/core.rs11
-rw-r--r--libs/debugstore/rust/src/storage.rs43
-rw-r--r--libs/ftl/Android.bp2
-rw-r--r--libs/ftl/finalizer_test.cpp209
-rw-r--r--libs/ftl/flags_test.cpp14
-rw-r--r--libs/ftl/ignore_test.cpp47
-rw-r--r--libs/ftl/non_null_test.cpp35
-rw-r--r--libs/graphicsenv/Android.bp22
-rw-r--r--libs/graphicsenv/FeatureOverrides.cpp209
-rw-r--r--libs/graphicsenv/GraphicsEnv.cpp49
-rw-r--r--libs/graphicsenv/IGpuService.cpp24
-rw-r--r--libs/graphicsenv/graphicsenv_flags.aconfig9
-rw-r--r--libs/graphicsenv/include/graphicsenv/FeatureOverrides.h59
-rw-r--r--libs/graphicsenv/include/graphicsenv/GraphicsEnv.h5
-rw-r--r--libs/graphicsenv/include/graphicsenv/IGpuService.h5
-rw-r--r--libs/gui/Android.bp8
-rw-r--r--libs/gui/BLASTBufferQueue.cpp336
-rw-r--r--libs/gui/BufferItem.cpp65
-rw-r--r--libs/gui/BufferItemConsumer.cpp27
-rw-r--r--libs/gui/BufferQueue.cpp9
-rw-r--r--libs/gui/BufferQueueConsumer.cpp133
-rw-r--r--libs/gui/BufferQueueCore.cpp49
-rw-r--r--libs/gui/BufferQueueProducer.cpp139
-rw-r--r--libs/gui/BufferReleaseChannel.cpp71
-rw-r--r--libs/gui/BufferStuffing.md7
-rw-r--r--libs/gui/Choreographer.cpp12
-rw-r--r--libs/gui/ConsumerBase.cpp120
-rw-r--r--libs/gui/DisplayEventDispatcher.cpp18
-rw-r--r--libs/gui/DisplayLuts.cpp81
-rw-r--r--libs/gui/Flags.cpp81
-rw-r--r--libs/gui/FrameRateUtils.cpp2
-rw-r--r--libs/gui/GLConsumer.cpp104
-rw-r--r--libs/gui/IConsumerListener.cpp122
-rw-r--r--libs/gui/IGraphicBufferConsumer.cpp230
-rw-r--r--libs/gui/IGraphicBufferProducer.cpp32
-rw-r--r--libs/gui/IGraphicBufferProducerFlattenables.cpp41
-rw-r--r--libs/gui/LayerState.cpp68
-rw-r--r--libs/gui/StreamSplitter.cpp3
-rw-r--r--libs/gui/Surface.cpp80
-rw-r--r--libs/gui/SurfaceComposerClient.cpp309
-rw-r--r--libs/gui/SurfaceControl.cpp3
-rw-r--r--libs/gui/TEST_MAPPING29
-rw-r--r--libs/gui/WindowInfo.cpp66
-rw-r--r--libs/gui/aidl/android/gui/ActivePicture.aidl32
-rw-r--r--libs/gui/aidl/android/gui/CaptureArgs.aidl5
-rw-r--r--libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl10
-rw-r--r--libs/gui/aidl/android/gui/FrameRateCategoryRate.aidl24
-rw-r--r--libs/gui/aidl/android/gui/IActivePictureListener.aidl30
-rw-r--r--libs/gui/aidl/android/gui/ISurfaceComposer.aidl18
-rw-r--r--libs/gui/aidl/android/gui/LutProperties.aidl32
-rw-r--r--libs/gui/aidl/android/gui/OverlayProperties.aidl4
-rw-r--r--libs/gui/include/gui/BLASTBufferQueue.h120
-rw-r--r--libs/gui/include/gui/BufferItem.h7
-rw-r--r--libs/gui/include/gui/BufferItemConsumer.h10
-rw-r--r--libs/gui/include/gui/BufferQueue.h5
-rw-r--r--libs/gui/include/gui/BufferQueueConsumer.h29
-rw-r--r--libs/gui/include/gui/BufferQueueCore.h30
-rw-r--r--libs/gui/include/gui/BufferQueueDefs.h7
-rw-r--r--libs/gui/include/gui/BufferQueueProducer.h13
-rw-r--r--libs/gui/include/gui/BufferReleaseChannel.h3
-rw-r--r--libs/gui/include/gui/BufferSlot.h28
-rw-r--r--libs/gui/include/gui/Choreographer.h3
-rw-r--r--libs/gui/include/gui/ConsumerBase.h17
-rw-r--r--libs/gui/include/gui/DisplayEventDispatcher.h2
-rw-r--r--libs/gui/include/gui/DisplayEventReceiver.h30
-rw-r--r--libs/gui/include/gui/DisplayLuts.h164
-rw-r--r--libs/gui/include/gui/Flags.h35
-rw-r--r--libs/gui/include/gui/GLConsumer.h27
-rw-r--r--libs/gui/include/gui/IConsumerListener.h32
-rw-r--r--libs/gui/include/gui/IGraphicBufferConsumer.h75
-rw-r--r--libs/gui/include/gui/IGraphicBufferProducer.h56
-rw-r--r--libs/gui/include/gui/LayerState.h52
-rw-r--r--libs/gui/include/gui/StreamSplitter.h2
-rw-r--r--libs/gui/include/gui/Surface.h8
-rw-r--r--libs/gui/include/gui/SurfaceComposerClient.h54
-rw-r--r--libs/gui/include/gui/VsyncEventData.h3
-rw-r--r--libs/gui/include/gui/WindowInfo.h9
-rw-r--r--libs/gui/include/gui/mock/GraphicBufferConsumer.h11
-rw-r--r--libs/gui/include/gui/view/Surface.h26
-rw-r--r--libs/gui/libgui_flags.aconfig35
-rw-r--r--libs/gui/tests/Android.bp1
-rw-r--r--libs/gui/tests/BLASTBufferQueue_test.cpp8
-rw-r--r--libs/gui/tests/BufferItemConsumer_test.cpp65
-rw-r--r--libs/gui/tests/BufferQueue_test.cpp399
-rw-r--r--libs/gui/tests/BufferReleaseChannel_test.cpp47
-rw-r--r--libs/gui/tests/Choreographer_test.cpp4
-rw-r--r--libs/gui/tests/CpuConsumer_test.cpp25
-rw-r--r--libs/gui/tests/DisconnectWaiter.h2
-rw-r--r--libs/gui/tests/DisplayEventStructLayout_test.cpp11
-rw-r--r--libs/gui/tests/EndToEndNativeInputTest.cpp318
-rw-r--r--libs/gui/tests/FillBuffer.cpp17
-rw-r--r--libs/gui/tests/FillBuffer.h2
-rw-r--r--libs/gui/tests/FrameRateUtilsTest.cpp4
-rw-r--r--libs/gui/tests/Malicious.cpp2
-rw-r--r--libs/gui/tests/MockConsumer.h2
-rw-r--r--libs/gui/tests/RegionSampling_test.cpp2
-rw-r--r--libs/gui/tests/SamplingDemo.cpp8
-rw-r--r--libs/gui/tests/StreamSplitter_test.cpp11
-rw-r--r--libs/gui/tests/SurfaceTextureGL_test.cpp28
-rw-r--r--libs/gui/tests/Surface_test.cpp164
-rw-r--r--libs/gui/tests/WindowInfo_test.cpp16
-rw-r--r--libs/gui/tests/benchmarks/Android.bp27
-rw-r--r--libs/gui/tests/benchmarks/Transaction_benchmarks.cpp139
-rw-r--r--libs/gui/tests/benchmarks/main.cpp18
-rw-r--r--libs/gui/tests/testserver/TestServer.cpp2
-rw-r--r--libs/gui/view/Surface.cpp36
-rw-r--r--libs/input/AccelerationCurve.cpp21
-rw-r--r--libs/input/Android.bp106
-rw-r--r--libs/input/CoordinateFilter.cpp31
-rw-r--r--libs/input/Input.cpp89
-rw-r--r--libs/input/InputConsumerNoResampling.cpp115
-rw-r--r--libs/input/InputDevice.cpp32
-rw-r--r--libs/input/InputEventLabels.cpp21
-rw-r--r--libs/input/InputFlags.cpp47
-rw-r--r--libs/input/InputTransport.cpp25
-rw-r--r--libs/input/InputVerifier.cpp17
-rw-r--r--libs/input/KeyCharacterMap.cpp21
-rw-r--r--libs/input/KeyboardClassifier.cpp8
-rw-r--r--libs/input/OneEuroFilter.cpp87
-rw-r--r--libs/input/Resampler.cpp278
-rw-r--r--libs/input/android/os/IInputConstants.aidl33
-rw-r--r--libs/input/android/os/InputConfig.aidl13
-rw-r--r--libs/input/android/os/MotionEventFlag.aidl17
-rw-r--r--libs/input/input_flags.aconfig120
-rw-r--r--libs/input/rust/Android.bp4
-rw-r--r--libs/input/rust/data_store.rs4
-rw-r--r--libs/input/rust/input.rs63
-rw-r--r--libs/input/rust/input_verifier.rs929
-rw-r--r--libs/input/rust/keyboard_classification_config.rs13
-rw-r--r--libs/input/rust/lib.rs106
-rw-r--r--libs/input/tests/Android.bp32
-rw-r--r--libs/input/tests/InputConsumerFilteredResampling_test.cpp218
-rw-r--r--libs/input/tests/InputConsumerResampling_test.cpp744
-rw-r--r--libs/input/tests/InputConsumer_test.cpp338
-rw-r--r--libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp87
-rw-r--r--libs/input/tests/InputVerifier_test.cpp10
-rw-r--r--libs/input/tests/OneEuroFilter_test.cpp137
-rw-r--r--libs/input/tests/Resampler_test.cpp47
-rw-r--r--libs/input/tests/TestEventMatchers.h116
-rw-r--r--libs/input/tests/TfLiteMotionPredictor_test.cpp16
-rw-r--r--libs/input/tests/TouchResampling_test.cpp5
-rw-r--r--libs/nativedisplay/AChoreographer.cpp2
-rw-r--r--libs/nativedisplay/include/surfacetexture/EGLConsumer.h15
-rw-r--r--libs/nativedisplay/include/surfacetexture/SurfaceTexture.h7
-rw-r--r--libs/nativedisplay/surfacetexture/EGLConsumer.cpp29
-rw-r--r--libs/nativedisplay/surfacetexture/ImageConsumer.cpp37
-rw-r--r--libs/nativedisplay/surfacetexture/SurfaceTexture.cpp8
-rw-r--r--libs/nativewindow/ANativeWindow.cpp2
-rw-r--r--libs/nativewindow/include/android/data_space.h7
-rw-r--r--libs/nativewindow/include/android/native_window.h12
-rw-r--r--libs/nativewindow/include/system/window.h7
-rw-r--r--libs/nativewindow/tests/ANativeWindowTest.cpp11
-rw-r--r--libs/permission/Android.bp2
-rw-r--r--libs/permission/IAppOpsCallback.cpp68
-rw-r--r--libs/permission/aidl/com/android/internal/app/IAppOpsCallback.aidl21
-rw-r--r--libs/permission/include/binder/AppOpsManager.h11
-rw-r--r--libs/permission/include/binder/IAppOpsCallback.h57
-rw-r--r--libs/permission/include/binder/IAppOpsService.h5
-rw-r--r--libs/renderengine/Android.bp1
-rw-r--r--libs/renderengine/RenderEngine.cpp9
-rw-r--r--libs/renderengine/benchmark/RenderEngineBench.cpp3
-rw-r--r--libs/renderengine/include/renderengine/LayerSettings.h9
-rw-r--r--libs/renderengine/include/renderengine/RenderEngine.h30
-rw-r--r--libs/renderengine/include/renderengine/mock/RenderEngine.h11
-rw-r--r--libs/renderengine/skia/Cache.cpp23
-rw-r--r--libs/renderengine/skia/GaneshVkRenderEngine.cpp8
-rw-r--r--libs/renderengine/skia/GaneshVkRenderEngine.h1
-rw-r--r--libs/renderengine/skia/GraphiteVkRenderEngine.cpp8
-rw-r--r--libs/renderengine/skia/GraphiteVkRenderEngine.h1
-rw-r--r--libs/renderengine/skia/SkiaGLRenderEngine.cpp2
-rw-r--r--libs/renderengine/skia/SkiaRenderEngine.cpp128
-rw-r--r--libs/renderengine/skia/SkiaRenderEngine.h16
-rw-r--r--libs/renderengine/skia/SkiaVkRenderEngine.cpp16
-rw-r--r--libs/renderengine/skia/SkiaVkRenderEngine.h2
-rw-r--r--libs/renderengine/skia/VulkanInterface.cpp8
-rw-r--r--libs/renderengine/skia/compat/GraphiteGpuContext.cpp32
-rw-r--r--libs/renderengine/skia/compat/GraphiteGpuContext.h12
-rw-r--r--libs/renderengine/skia/filters/BlurFilter.cpp2
-rw-r--r--libs/renderengine/skia/filters/GaussianBlurFilter.cpp2
-rw-r--r--libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp135
-rw-r--r--libs/renderengine/skia/filters/KawaseBlurDualFilter.h7
-rw-r--r--libs/renderengine/skia/filters/KawaseBlurFilter.cpp2
-rw-r--r--libs/renderengine/skia/filters/LutShader.cpp301
-rw-r--r--libs/renderengine/skia/filters/LutShader.h45
-rw-r--r--libs/renderengine/skia/filters/MouriMap.cpp24
-rw-r--r--libs/renderengine/skia/filters/MouriMap.h11
-rw-r--r--libs/renderengine/threaded/RenderEngineThreaded.cpp26
-rw-r--r--libs/renderengine/threaded/RenderEngineThreaded.h22
-rw-r--r--libs/sensor/Android.bp1
-rw-r--r--libs/sensor/Sensor.cpp18
-rw-r--r--libs/tracing_perfetto/Android.bp10
-rw-r--r--libs/tracing_perfetto/include/tracing_sdk.h451
-rw-r--r--libs/tracing_perfetto/tests/Android.bp4
-rw-r--r--libs/tracing_perfetto/tests/include/utils.h124
-rw-r--r--libs/tracing_perfetto/tests/tracing_perfetto_test.cpp1
-rw-r--r--libs/tracing_perfetto/tests/utils.cpp85
-rw-r--r--libs/tracing_perfetto/tests/utils.h457
-rw-r--r--libs/tracing_perfetto/tracing_perfetto.cpp35
-rw-r--r--libs/tracing_perfetto/tracing_perfetto_internal.cpp70
-rw-r--r--libs/tracing_perfetto/tracing_perfetto_internal.h2
-rw-r--r--libs/tracing_perfetto/tracing_sdk.cpp302
-rw-r--r--libs/ui/Android.bp1
-rw-r--r--libs/ui/DisplayIdentification.cpp69
-rw-r--r--libs/ui/GraphicBufferAllocator.cpp4
-rw-r--r--libs/ui/PictureProfileHandle.cpp29
-rw-r--r--libs/ui/PublicFormat.cpp7
-rw-r--r--libs/ui/include/ui/DisplayId.h79
-rw-r--r--libs/ui/include/ui/DisplayIdentification.h10
-rw-r--r--libs/ui/include/ui/DisplayMap.h3
-rw-r--r--libs/ui/include/ui/DynamicDisplayInfo.h9
-rw-r--r--libs/ui/include/ui/FloatRect.h3
-rw-r--r--libs/ui/include/ui/FrameRateCategoryRate.h35
-rw-r--r--libs/ui/include/ui/PictureProfileHandle.h60
-rw-r--r--libs/ui/include/ui/PublicFormat.h1
-rw-r--r--libs/ui/include/ui/Rect.h10
-rw-r--r--libs/ui/include_types/ui/HdrRenderTypeUtils.h27
-rw-r--r--libs/ui/tests/DisplayId_test.cpp24
-rw-r--r--libs/ui/tests/DisplayIdentification_test.cpp66
-rw-r--r--libs/ui/tests/Rect_test.cpp10
-rw-r--r--libs/vibrator/TEST_MAPPING2
249 files changed, 10521 insertions, 3150 deletions
diff --git a/libs/battery/LongArrayMultiStateCounter.cpp b/libs/battery/LongArrayMultiStateCounter.cpp
index 125cfaffa4..334d84b6b5 100644
--- a/libs/battery/LongArrayMultiStateCounter.cpp
+++ b/libs/battery/LongArrayMultiStateCounter.cpp
@@ -21,58 +21,137 @@
namespace android {
namespace battery {
-template <>
-bool LongArrayMultiStateCounter::delta(const std::vector<uint64_t>& previousValue,
- const std::vector<uint64_t>& newValue,
- std::vector<uint64_t>* outValue) const {
- size_t size = previousValue.size();
- if (newValue.size() != size) {
- ALOGE("Incorrect array size: %d, should be %d", (int)newValue.size(), (int)size);
- return false;
+Uint64ArrayRW::Uint64ArrayRW(const Uint64Array &copy) : Uint64Array(copy.size()) {
+ if (mSize != 0 && copy.data() != nullptr) {
+ mData = new uint64_t[mSize];
+ memcpy(mData, copy.data(), mSize * sizeof(uint64_t));
+ } else {
+ mData = nullptr;
}
+}
- bool is_delta_valid = true;
- for (int i = size - 1; i >= 0; i--) {
- if (newValue[i] >= previousValue[i]) {
- (*outValue)[i] = newValue[i] - previousValue[i];
+uint64_t *Uint64ArrayRW::dataRW() {
+ if (mData == nullptr) {
+ mData = new uint64_t[mSize];
+ memset(mData, 0, mSize * sizeof(uint64_t));
+ }
+ return mData;
+}
+
+Uint64ArrayRW &Uint64ArrayRW::operator=(const Uint64Array &t) {
+ if (t.size() != mSize) {
+ delete[] mData;
+ mSize = t.size();
+ mData = nullptr;
+ }
+ if (mSize != 0) {
+ if (t.data() != nullptr) {
+ if (mData == nullptr) {
+ mData = new uint64_t[mSize];
+ }
+ memcpy(mData, t.data(), mSize * sizeof(uint64_t));
} else {
- (*outValue)[i] = 0;
- is_delta_valid = false;
+ delete[] mData;
+ mData = nullptr;
}
}
- return is_delta_valid;
+ return *this;
+}
+
+std::ostream &operator<<(std::ostream &os, const Uint64Array &v) {
+ os << "{";
+ const uint64_t *data = v.data();
+ if (data != nullptr) {
+ bool first = true;
+ for (size_t i = 0; i < v.size(); i++) {
+ if (!first) {
+ os << ", ";
+ }
+ os << data[i];
+ first = false;
+ }
+ }
+ os << "}";
+ return os;
+}
+
+// Convenience constructor for tests
+Uint64ArrayRW::Uint64ArrayRW(std::initializer_list<uint64_t> init) : Uint64Array(init.size()) {
+ mData = new uint64_t[mSize];
+ memcpy(mData, init.begin(), mSize * sizeof(uint64_t));
+}
+
+// Used in tests only.
+bool Uint64Array::operator==(const Uint64Array &other) const {
+ if (size() != other.size()) {
+ return false;
+ }
+ const uint64_t* thisData = data();
+ const uint64_t* thatData = other.data();
+ for (size_t i = 0; i < mSize; i++) {
+ const uint64_t v1 = thisData != nullptr ? thisData[i] : 0;
+ const uint64_t v2 = thatData != nullptr ? thatData[i] : 0;
+ if (v1 != v2) {
+ return false;
+ }
+ }
+ return true;
}
template <>
-void LongArrayMultiStateCounter::add(std::vector<uint64_t>* value1,
- const std::vector<uint64_t>& value2, const uint64_t numerator,
- const uint64_t denominator) const {
+void LongArrayMultiStateCounter::add(Uint64ArrayRW *value1, const Uint64Array &value2,
+ const uint64_t numerator, const uint64_t denominator) const {
+ const uint64_t* data2 = value2.data();
+ if (data2 == nullptr) {
+ return;
+ }
+
+ uint64_t* data1 = value1->dataRW();
+ size_t size = value2.size();
if (numerator != denominator) {
- for (int i = value2.size() - 1; i >= 0; i--) {
+ for (size_t i = 0; i < size; i++) {
// The caller ensures that denominator != 0
- (*value1)[i] += value2[i] * numerator / denominator;
+ data1[i] += data2[i] * numerator / denominator;
}
} else {
- for (int i = value2.size() - 1; i >= 0; i--) {
- (*value1)[i] += value2[i];
+ for (size_t i = 0; i < size; i++) {
+ data1[i] += data2[i];
}
}
}
-template <>
-std::string LongArrayMultiStateCounter::valueToString(const std::vector<uint64_t>& v) const {
- std::stringstream s;
- s << "{";
- bool first = true;
- for (uint64_t n : v) {
- if (!first) {
- s << ", ";
+template<>
+bool LongArrayMultiStateCounter::delta(const Uint64ArrayRW &previousValue,
+ const Uint64Array &newValue, Uint64ArrayRW *outValue) const {
+ size_t size = previousValue.size();
+ if (newValue.size() != size) {
+ ALOGE("Incorrect array size: %d, should be %d", (int) newValue.size(), (int) size);
+ return false;
+ }
+ if (outValue->size() != size) {
+ ALOGE("Incorrect outValue size: %d, should be %d", (int) outValue->size(), (int) size);
+ return false;
+ }
+
+ bool is_delta_valid = true;
+ const uint64_t *prevData = previousValue.data();
+ const uint64_t *newData = newValue.data();
+ uint64_t *outData = outValue->dataRW();
+ for (size_t i = 0; i < size; i++) {
+ if (prevData == nullptr) {
+ if (newData == nullptr) {
+ outData[i] = 0;
+ } else {
+ outData[i] = newData[i];
+ }
+ } else if (newData == nullptr || newData[i] < prevData[i]) {
+ outData[i] = 0;
+ is_delta_valid = false;
+ } else {
+ outData[i] = newData[i] - prevData[i];
}
- s << n;
- first = false;
}
- s << "}";
- return s.str();
+ return is_delta_valid;
}
} // namespace battery
diff --git a/libs/battery/LongArrayMultiStateCounter.h b/libs/battery/LongArrayMultiStateCounter.h
index f3439f6a0c..e00c96898e 100644
--- a/libs/battery/LongArrayMultiStateCounter.h
+++ b/libs/battery/LongArrayMultiStateCounter.h
@@ -23,7 +23,66 @@
namespace android {
namespace battery {
-typedef MultiStateCounter<std::vector<uint64_t>> LongArrayMultiStateCounter;
+/**
+ * Wrapper for an array of uint64's.
+ */
+class Uint64Array {
+ protected:
+ size_t mSize;
+
+ public:
+ Uint64Array() : Uint64Array(0) {}
+
+ Uint64Array(size_t size) : mSize(size) {}
+
+ virtual ~Uint64Array() {}
+
+ size_t size() const { return mSize; }
+
+ /**
+ * Returns the wrapped array.
+ *
+ * Nullable! Null should be interpreted the same as an array of zeros
+ */
+ virtual const uint64_t *data() const { return nullptr; }
+
+ friend std::ostream &operator<<(std::ostream &os, const Uint64Array &v);
+
+ // Test API
+ bool operator==(const Uint64Array &other) const;
+};
+
+/**
+ * Mutable version of Uint64Array.
+ */
+class Uint64ArrayRW: public Uint64Array {
+ uint64_t* mData;
+
+public:
+ Uint64ArrayRW() : Uint64ArrayRW(0) {}
+
+ Uint64ArrayRW(size_t size) : Uint64Array(size), mData(nullptr) {}
+
+ Uint64ArrayRW(const Uint64Array &copy);
+
+ // Need an explicit copy constructor. In the initialization context C++ does not understand that
+ // a Uint64ArrayRW is a Uint64Array.
+ Uint64ArrayRW(const Uint64ArrayRW &copy) : Uint64ArrayRW((const Uint64Array &) copy) {}
+
+ // Test API
+ Uint64ArrayRW(std::initializer_list<uint64_t> init);
+
+ ~Uint64ArrayRW() override { delete[] mData; }
+
+ const uint64_t *data() const override { return mData; }
+
+ // NonNull. Will initialize the wrapped array if it is null.
+ uint64_t *dataRW();
+
+ Uint64ArrayRW &operator=(const Uint64Array &t);
+};
+
+typedef MultiStateCounter<Uint64ArrayRW, Uint64Array> LongArrayMultiStateCounter;
} // namespace battery
} // namespace android
diff --git a/libs/battery/LongArrayMultiStateCounterTest.cpp b/libs/battery/LongArrayMultiStateCounterTest.cpp
index e4e6b2a49f..1c74e3fd14 100644
--- a/libs/battery/LongArrayMultiStateCounterTest.cpp
+++ b/libs/battery/LongArrayMultiStateCounterTest.cpp
@@ -24,25 +24,25 @@ namespace battery {
class LongArrayMultiStateCounterTest : public testing::Test {};
TEST_F(LongArrayMultiStateCounterTest, stateChange) {
- LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4));
- testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000);
+ LongArrayMultiStateCounter testCounter(2, Uint64Array(4));
+ testCounter.updateValue(Uint64ArrayRW({0, 0, 0, 0}), 1000);
testCounter.setState(0, 1000);
testCounter.setState(1, 2000);
- testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000);
+ testCounter.updateValue(Uint64ArrayRW({100, 200, 300, 400}), 3000);
// Time was split in half between the two states, so the counts will be split 50:50 too
- EXPECT_EQ(std::vector<uint64_t>({50, 100, 150, 200}), testCounter.getCount(0));
- EXPECT_EQ(std::vector<uint64_t>({50, 100, 150, 200}), testCounter.getCount(1));
+ EXPECT_EQ(Uint64ArrayRW({50, 100, 150, 200}), testCounter.getCount(0));
+ EXPECT_EQ(Uint64ArrayRW({50, 100, 150, 200}), testCounter.getCount(1));
}
TEST_F(LongArrayMultiStateCounterTest, accumulation) {
- LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4));
- testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000);
+ LongArrayMultiStateCounter testCounter(2, Uint64Array(4));
+ testCounter.updateValue(Uint64ArrayRW({0, 0, 0, 0}), 1000);
testCounter.setState(0, 1000);
testCounter.setState(1, 2000);
- testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000);
+ testCounter.updateValue(Uint64ArrayRW({100, 200, 300, 400}), 3000);
testCounter.setState(0, 4000);
- testCounter.updateValue(std::vector<uint64_t>({200, 300, 400, 500}), 8000);
+ testCounter.updateValue(Uint64ArrayRW({200, 300, 400, 500}), 8000);
// The first delta is split 50:50:
// 0: {50, 100, 150, 200}
@@ -50,16 +50,16 @@ TEST_F(LongArrayMultiStateCounterTest, accumulation) {
// The second delta is split 4:1
// 0: {80, 80, 80, 80}
// 1: {20, 20, 20, 20}
- EXPECT_EQ(std::vector<uint64_t>({130, 180, 230, 280}), testCounter.getCount(0));
- EXPECT_EQ(std::vector<uint64_t>({70, 120, 170, 220}), testCounter.getCount(1));
+ EXPECT_EQ(Uint64ArrayRW({130, 180, 230, 280}), testCounter.getCount(0));
+ EXPECT_EQ(Uint64ArrayRW({70, 120, 170, 220}), testCounter.getCount(1));
}
TEST_F(LongArrayMultiStateCounterTest, toString) {
- LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4));
- testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000);
+ LongArrayMultiStateCounter testCounter(2, Uint64Array(4));
+ testCounter.updateValue(Uint64ArrayRW({0, 0, 0, 0}), 1000);
testCounter.setState(0, 1000);
testCounter.setState(1, 2000);
- testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000);
+ testCounter.updateValue(Uint64ArrayRW({100, 200, 300, 400}), 3000);
EXPECT_STREQ("[0: {50, 100, 150, 200}, 1: {50, 100, 150, 200}] updated: 3000 currentState: 1",
testCounter.toString().c_str());
diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h
index 04b718698e..fadc4ffd41 100644
--- a/libs/battery/MultiStateCounter.h
+++ b/libs/battery/MultiStateCounter.h
@@ -35,12 +35,12 @@ namespace battery {
typedef uint16_t state_t;
-template <class T>
+template <class T, class V>
class MultiStateCounter {
- uint16_t stateCount;
+ const uint16_t stateCount;
+ const V emptyValue;
state_t currentState;
time_t lastStateChangeTimestamp;
- T emptyValue;
T lastValue;
time_t lastUpdateTimestamp;
T deltaValue;
@@ -54,7 +54,7 @@ class MultiStateCounter {
State* states;
public:
- MultiStateCounter(uint16_t stateCount, const T& emptyValue);
+ MultiStateCounter(uint16_t stateCount, const V& emptyValue);
virtual ~MultiStateCounter();
@@ -66,35 +66,35 @@ public:
* Copies the current state and accumulated times-in-state from the source. Resets
* the accumulated value.
*/
- void copyStatesFrom(const MultiStateCounter<T>& source);
+ void copyStatesFrom(const MultiStateCounter<T, V> &source);
- void setValue(state_t state, const T& value);
+ void setValue(state_t state, const V& value);
/**
* Updates the value by distributing the delta from the previously set value
* among states according to their respective time-in-state.
* Returns the delta from the previously set value.
*/
- const T& updateValue(const T& value, time_t timestamp);
+ const V& updateValue(const V& value, time_t timestamp);
/**
* Updates the value by distributing the specified increment among states according
* to their respective time-in-state.
*/
- void incrementValue(const T& increment, time_t timestamp);
+ void incrementValue(const V& increment, time_t timestamp);
/**
* Adds the specified increment to the value for the current state, without affecting
* the last updated value or timestamp. Ignores partial time-in-state: the entirety of
* the increment is given to the current state.
*/
- void addValue(const T& increment);
+ void addValue(const V& increment);
void reset();
uint16_t getStateCount();
- const T& getCount(state_t state);
+ const V& getCount(state_t state);
std::string toString();
@@ -104,27 +104,25 @@ private:
* Returns true iff the combination of previousValue and newValue is valid
* (newValue >= prevValue)
*/
- bool delta(const T& previousValue, const T& newValue, T* outValue) const;
+ bool delta(const T& previousValue, const V& newValue, T* outValue) const;
/**
* Adds value2 to value1 and stores the result in value1. Denominator is
* guaranteed to be non-zero.
*/
- void add(T* value1, const T& value2, const uint64_t numerator,
+ void add(T* value1, const V& value2, const uint64_t numerator,
const uint64_t denominator) const;
-
- std::string valueToString(const T& value) const;
};
// ---------------------- MultiStateCounter Implementation -------------------------
// Since MultiStateCounter is a template, the implementation must be inlined.
-template <class T>
-MultiStateCounter<T>::MultiStateCounter(uint16_t stateCount, const T& emptyValue)
+template <class T, class V>
+MultiStateCounter<T, V>::MultiStateCounter(uint16_t stateCount, const V& emptyValue)
: stateCount(stateCount),
+ emptyValue(emptyValue),
currentState(0),
lastStateChangeTimestamp(-1),
- emptyValue(emptyValue),
lastValue(emptyValue),
lastUpdateTimestamp(-1),
deltaValue(emptyValue),
@@ -136,13 +134,13 @@ MultiStateCounter<T>::MultiStateCounter(uint16_t stateCount, const T& emptyValue
}
}
-template <class T>
-MultiStateCounter<T>::~MultiStateCounter() {
+template <class T, class V>
+MultiStateCounter<T, V>::~MultiStateCounter() {
delete[] states;
};
-template <class T>
-void MultiStateCounter<T>::setEnabled(bool enabled, time_t timestamp) {
+template <class T, class V>
+void MultiStateCounter<T, V>::setEnabled(bool enabled, time_t timestamp) {
if (enabled == isEnabled) {
return;
}
@@ -167,8 +165,8 @@ void MultiStateCounter<T>::setEnabled(bool enabled, time_t timestamp) {
}
}
-template <class T>
-void MultiStateCounter<T>::setState(state_t state, time_t timestamp) {
+template <class T, class V>
+void MultiStateCounter<T, V>::setState(state_t state, time_t timestamp) {
if (isEnabled && lastStateChangeTimestamp >= 0 && lastUpdateTimestamp >= 0) {
// If the update arrived out-of-order, just push back the timestamp to
// avoid having the situation where timeInStateSinceUpdate > timeSinceUpdate
@@ -198,8 +196,8 @@ void MultiStateCounter<T>::setState(state_t state, time_t timestamp) {
lastStateChangeTimestamp = timestamp;
}
-template <class T>
-void MultiStateCounter<T>::copyStatesFrom(const MultiStateCounter<T>& source) {
+template <class T, class V>
+void MultiStateCounter<T, V>::copyStatesFrom(const MultiStateCounter<T, V>& source) {
if (stateCount != source.stateCount) {
ALOGE("State count mismatch: %u vs. %u\n", stateCount, source.stateCount);
return;
@@ -214,14 +212,14 @@ void MultiStateCounter<T>::copyStatesFrom(const MultiStateCounter<T>& source) {
lastUpdateTimestamp = source.lastUpdateTimestamp;
}
-template <class T>
-void MultiStateCounter<T>::setValue(state_t state, const T& value) {
+template <class T, class V>
+void MultiStateCounter<T, V>::setValue(state_t state, const V& value) {
states[state].counter = value;
}
-template <class T>
-const T& MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) {
- T* returnValue = &emptyValue;
+template <class T, class V>
+const V& MultiStateCounter<T, V>::updateValue(const V& value, time_t timestamp) {
+ const V* returnValue = &emptyValue;
// If the counter is disabled, we ignore the update, except when the counter got disabled after
// the previous update, in which case we still need to pick up the residual delta.
@@ -250,8 +248,8 @@ const T& MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) {
}
} else {
std::stringstream str;
- str << "updateValue is called with a value " << valueToString(value)
- << ", which is lower than the previous value " << valueToString(lastValue)
+ str << "updateValue is called with a value " << value
+ << ", which is lower than the previous value " << lastValue
<< "\n";
ALOGE("%s", str.str().c_str());
@@ -276,23 +274,25 @@ const T& MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) {
return *returnValue;
}
-template <class T>
-void MultiStateCounter<T>::incrementValue(const T& increment, time_t timestamp) {
+template <class T, class V>
+void MultiStateCounter<T, V>::incrementValue(const V& increment, time_t timestamp) {
+// T newValue;
+// newValue = lastValue; // Copy assignment, not initialization.
T newValue = lastValue;
add(&newValue, increment, 1 /* numerator */, 1 /* denominator */);
updateValue(newValue, timestamp);
}
-template <class T>
-void MultiStateCounter<T>::addValue(const T& value) {
+template <class T, class V>
+void MultiStateCounter<T, V>::addValue(const V& value) {
if (!isEnabled) {
return;
}
add(&states[currentState].counter, value, 1 /* numerator */, 1 /* denominator */);
}
-template <class T>
-void MultiStateCounter<T>::reset() {
+template <class T, class V>
+void MultiStateCounter<T, V>::reset() {
lastStateChangeTimestamp = -1;
lastUpdateTimestamp = -1;
for (int i = 0; i < stateCount; i++) {
@@ -301,25 +301,26 @@ void MultiStateCounter<T>::reset() {
}
}
-template <class T>
-uint16_t MultiStateCounter<T>::getStateCount() {
+template <class T, class V>
+uint16_t MultiStateCounter<T, V>::getStateCount() {
return stateCount;
}
-template <class T>
-const T& MultiStateCounter<T>::getCount(state_t state) {
+template <class T, class V>
+const V& MultiStateCounter<T, V>::getCount(state_t state) {
return states[state].counter;
}
-template <class T>
-std::string MultiStateCounter<T>::toString() {
+template <class T, class V>
+std::string MultiStateCounter<T, V>::toString() {
std::stringstream str;
+// str << "LAST VALUE: " << valueToString(lastValue);
str << "[";
for (int i = 0; i < stateCount; i++) {
if (i != 0) {
str << ", ";
}
- str << i << ": " << valueToString(states[i].counter);
+ str << i << ": " << states[i].counter;
if (states[i].timeInStateSinceUpdate > 0) {
str << " timeInStateSinceUpdate: " << states[i].timeInStateSinceUpdate;
}
diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp
index a51a38a6c7..589b7fe4e3 100644
--- a/libs/battery/MultiStateCounterTest.cpp
+++ b/libs/battery/MultiStateCounterTest.cpp
@@ -21,7 +21,7 @@
namespace android {
namespace battery {
-typedef MultiStateCounter<double> DoubleMultiStateCounter;
+typedef MultiStateCounter<double, double> DoubleMultiStateCounter;
template <>
bool DoubleMultiStateCounter::delta(const double& previousValue, const double& newValue,
@@ -41,11 +41,6 @@ void DoubleMultiStateCounter::add(double* value1, const double& value2, const ui
}
}
-template <>
-std::string DoubleMultiStateCounter::valueToString(const double& v) const {
- return std::to_string(v);
-}
-
class MultiStateCounterTest : public testing::Test {};
TEST_F(MultiStateCounterTest, constructor) {
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 9d9ae31ccf..ea5343a2a0 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -269,6 +269,7 @@ cc_defaults {
"-Wzero-as-null-pointer-constant",
"-Wreorder-init-list",
"-Wunused-const-variable",
+ "-Wunused-result",
"-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
"-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
// Hide symbols by default and set the BUILDING_LIBBINDER macro so that
diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index 152c815ec3..83f4719de2 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -147,9 +147,11 @@ public:
data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
data.writeInt32(uid);
data.writeString16(callingPackage);
- remote()->transact(IS_UID_ACTIVE_TRANSACTION, data, &reply);
+ status_t err = remote()->transact(IS_UID_ACTIVE_TRANSACTION, data, &reply);
// fail on exception
- if (reply.readExceptionCode() != 0) return false;
+ if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+ return false;
+ }
return reply.readInt32() == 1;
}
@@ -159,9 +161,9 @@ public:
data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor());
data.writeInt32(uid);
data.writeString16(callingPackage);
- remote()->transact(GET_UID_PROCESS_STATE_TRANSACTION, data, &reply);
+ status_t err = remote()->transact(GET_UID_PROCESS_STATE_TRANSACTION, data, &reply);
// fail on exception
- if (reply.readExceptionCode() != 0) {
+ if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
return ActivityManager::PROCESS_STATE_UNKNOWN;
}
return reply.readInt32();
@@ -192,7 +194,7 @@ public:
data.writeInt32(appPid);
status_t err = remote()->transact(LOG_FGS_API_BEGIN_TRANSACTION, data, &reply,
IBinder::FLAG_ONEWAY);
- if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+ if (err != NO_ERROR) {
ALOGD("%s: FGS Logger Transaction failed, %d", __func__, err);
return err;
}
@@ -207,7 +209,7 @@ public:
data.writeInt32(appPid);
status_t err =
remote()->transact(LOG_FGS_API_END_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
- if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+ if (err != NO_ERROR) {
ALOGD("%s: FGS Logger Transaction failed, %d", __func__, err);
return err;
}
@@ -224,7 +226,7 @@ public:
data.writeInt32(appPid);
status_t err = remote()->transact(LOG_FGS_API_STATE_CHANGED_TRANSACTION, data, &reply,
IBinder::FLAG_ONEWAY);
- if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+ if (err != NO_ERROR) {
ALOGD("%s: FGS Logger Transaction failed, %d", __func__, err);
return err;
}
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index f7e0915f15..1c1b6f30a4 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -630,12 +630,22 @@ void IPCThreadState::flushCommands()
{
if (mProcess->mDriverFD < 0)
return;
- talkWithDriver(false);
+
+ if (status_t res = talkWithDriver(false); res != OK) {
+ // TODO: we may want to abort for some of these cases
+ ALOGW("1st call to talkWithDriver returned error in flushCommands: %s",
+ statusToString(res).c_str());
+ }
+
// The flush could have caused post-write refcount decrements to have
// been executed, which in turn could result in BC_RELEASE/BC_DECREFS
// being queued in mOut. So flush again, if we need to.
if (mOut.dataSize() > 0) {
- talkWithDriver(false);
+ if (status_t res = talkWithDriver(false); res != OK) {
+ // TODO: we may want to abort for some of these cases
+ ALOGW("2nd call to talkWithDriver returned error in flushCommands: %s",
+ statusToString(res).c_str());
+ }
}
if (mOut.dataSize() > 0) {
ALOGW("mOut.dataSize() > 0 after flushCommands()");
@@ -807,7 +817,11 @@ void IPCThreadState::joinThreadPool(bool isMain)
mOut.writeInt32(BC_EXIT_LOOPER);
mIsLooper = false;
- talkWithDriver(false);
+ if (status_t res = talkWithDriver(false); res != OK) {
+ // TODO: we may want to abort for some of these cases
+ ALOGW("call to talkWithDriver in joinThreadPool returned error: %s, FD: %d",
+ statusToString(res).c_str(), mProcess->mDriverFD);
+ }
size_t oldCount = mProcess->mCurrentThreads.fetch_sub(1);
LOG_ALWAYS_FATAL_IF(oldCount == 0,
"Threadpool thread count underflowed. Thread cannot exist and exit in "
@@ -844,7 +858,7 @@ status_t IPCThreadState::handlePolledCommands()
void IPCThreadState::stopProcess(bool /*immediate*/)
{
//ALOGI("**** STOPPING PROCESS");
- flushCommands();
+ (void)flushCommands();
int fd = mProcess->mDriverFD;
mProcess->mDriverFD = -1;
close(fd);
@@ -1498,7 +1512,14 @@ status_t IPCThreadState::executeCommand(int32_t cmd)
buffer.setDataSize(0);
constexpr uint32_t kForwardReplyFlags = TF_CLEAR_BUF;
- sendReply(reply, (tr.flags & kForwardReplyFlags));
+
+ // TODO: we may want to abort if there is an error here, or return as 'error'
+ // from this function, but the impact needs to be measured
+ status_t error2 = sendReply(reply, (tr.flags & kForwardReplyFlags));
+ if (error2 != OK) {
+ ALOGE("error in sendReply for synchronous call: %s",
+ statusToString(error2).c_str());
+ }
} else {
if (error != OK) {
std::ostringstream logStream;
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 1e83c350b1..777c22a63e 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -156,7 +156,7 @@ enum {
#ifdef BINDER_WITH_KERNEL_IPC
static void acquire_object(const sp<ProcessState>& proc, const flat_binder_object& obj,
- const void* who) {
+ const void* who, bool tagFds) {
switch (obj.hdr.type) {
case BINDER_TYPE_BINDER:
if (obj.binder) {
@@ -173,14 +173,14 @@ static void acquire_object(const sp<ProcessState>& proc, const flat_binder_objec
return;
}
case BINDER_TYPE_FD: {
- if (obj.cookie != 0) { // owned
+ if (tagFds && obj.cookie != 0) { // owned
FdTag(obj.handle, nullptr, who);
}
return;
}
}
- ALOGD("Invalid object type 0x%08x", obj.hdr.type);
+ ALOGE("Invalid object type 0x%08x to acquire", obj.hdr.type);
}
static void release_object(const sp<ProcessState>& proc, const flat_binder_object& obj,
@@ -210,7 +210,7 @@ static void release_object(const sp<ProcessState>& proc, const flat_binder_objec
}
}
- ALOGE("Invalid object type 0x%08x", obj.hdr.type);
+ ALOGE("Invalid object type 0x%08x to release", obj.hdr.type);
}
#endif // BINDER_WITH_KERNEL_IPC
@@ -616,7 +616,7 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) {
}
}
- acquire_object(proc, *flat, this);
+ acquire_object(proc, *flat, this, true /*tagFds*/);
}
}
#else
@@ -1162,31 +1162,6 @@ status_t Parcel::finishWrite(size_t len)
return NO_ERROR;
}
-status_t Parcel::writeUnpadded(const void* data, size_t len)
-{
- if (len > INT32_MAX) {
- // don't accept size_t values which may have come from an
- // inadvertent conversion from a negative int.
- return BAD_VALUE;
- }
-
- size_t end = mDataPos + len;
- if (end < mDataPos) {
- // integer overflow
- return BAD_VALUE;
- }
-
- if (end <= mDataCapacity) {
-restart_write:
- memcpy(mData+mDataPos, data, len);
- return finishWrite(len);
- }
-
- status_t err = growData(len);
- if (err == NO_ERROR) goto restart_write;
- return err;
-}
-
status_t Parcel::write(const void* data, size_t len)
{
if (len > INT32_MAX) {
@@ -1223,6 +1198,10 @@ restart_write:
//printf("Writing %ld bytes, padded to %ld\n", len, padded);
uint8_t* const data = mData+mDataPos;
+ if (status_t status = validateReadData(mDataPos + padded); status != OK) {
+ return nullptr; // drops status
+ }
+
// Need to pad at end?
if (padded != len) {
#if BYTE_ORDER == BIG_ENDIAN
@@ -1803,6 +1782,10 @@ status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData)
const bool enoughObjects = kernelFields->mObjectsSize < kernelFields->mObjectsCapacity;
if (enoughData && enoughObjects) {
restart_write:
+ if (status_t status = validateReadData(mDataPos + sizeof(val)); status != OK) {
+ return status;
+ }
+
*reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val;
// remember if it's a file descriptor
@@ -1817,13 +1800,22 @@ restart_write:
// Need to write meta-data?
if (nullMetaData || val.binder != 0) {
kernelFields->mObjects[kernelFields->mObjectsSize] = mDataPos;
- acquire_object(ProcessState::self(), val, this);
+ acquire_object(ProcessState::self(), val, this, true /*tagFds*/);
kernelFields->mObjectsSize++;
}
return finishWrite(sizeof(flat_binder_object));
}
+ if (mOwner) {
+ // continueWrite does have the logic to convert this from an
+ // owned to an unowned Parcel. However, this is pretty inefficient,
+ // and it's really strange to need to do so, so prefer to avoid
+ // these paths than try to support them.
+ ALOGE("writing objects not supported on owned Parcels");
+ return PERMISSION_DENIED;
+ }
+
if (!enoughData) {
const status_t err = growData(sizeof(val));
if (err != NO_ERROR) return err;
@@ -1878,7 +1870,10 @@ status_t Parcel::validateReadData(size_t upperBound) const
if (mDataPos < kernelFields->mObjects[nextObject] + sizeof(flat_binder_object)) {
// Requested info overlaps with an object
if (!mServiceFuzzing) {
- ALOGE("Attempt to read from protected data in Parcel %p", this);
+ ALOGE("Attempt to read or write from protected data in Parcel %p. pos: "
+ "%zu, nextObject: %zu, object offset: %llu, object size: %zu",
+ this, mDataPos, nextObject, kernelFields->mObjects[nextObject],
+ sizeof(flat_binder_object));
}
return PERMISSION_DENIED;
}
@@ -2046,6 +2041,10 @@ status_t Parcel::writeAligned(T val) {
if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
+ if (status_t status = validateReadData(mDataPos + sizeof(val)); status != OK) {
+ return status;
+ }
+
memcpy(mData + mDataPos, &val, sizeof(val));
return finishWrite(sizeof(val));
}
@@ -2236,9 +2235,7 @@ const char* Parcel::readCString() const
const char* eos = reinterpret_cast<const char*>(memchr(str, 0, avail));
if (eos) {
const size_t len = eos - str;
- mDataPos += pad_size(len+1);
- ALOGV("readCString Setting data pos of %p to %zu", this, mDataPos);
- return str;
+ return static_cast<const char*>(readInplace(len + 1));
}
}
return nullptr;
@@ -2682,14 +2679,14 @@ const flat_binder_object* Parcel::readObject(bool nullMetaData) const
}
#endif // BINDER_WITH_KERNEL_IPC
-void Parcel::closeFileDescriptors() {
+void Parcel::closeFileDescriptors(size_t newObjectsSize) {
if (auto* kernelFields = maybeKernelFields()) {
#ifdef BINDER_WITH_KERNEL_IPC
size_t i = kernelFields->mObjectsSize;
if (i > 0) {
// ALOGI("Closing file descriptors for %zu objects...", i);
}
- while (i > 0) {
+ while (i > newObjectsSize) {
i--;
const flat_binder_object* flat =
reinterpret_cast<flat_binder_object*>(mData + kernelFields->mObjects[i]);
@@ -2700,6 +2697,7 @@ void Parcel::closeFileDescriptors() {
}
}
#else // BINDER_WITH_KERNEL_IPC
+ (void)newObjectsSize;
LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
(void)kernelFields;
#endif // BINDER_WITH_KERNEL_IPC
@@ -2734,6 +2732,65 @@ size_t Parcel::ipcObjectsCount() const
return 0;
}
+static void do_nothing_release_func(const uint8_t* data, size_t dataSize,
+ const binder_size_t* objects, size_t objectsCount) {
+ (void)data;
+ (void)dataSize;
+ (void)objects;
+ (void)objectsCount;
+}
+static void delete_data_release_func(const uint8_t* data, size_t dataSize,
+ const binder_size_t* objects, size_t objectsCount) {
+ delete[] data;
+ (void)dataSize;
+ (void)objects;
+ (void)objectsCount;
+}
+
+void Parcel::makeDangerousViewOf(Parcel* p) {
+ if (p->isForRpc()) {
+ // warning: this must match the logic in rpcSetDataReference
+ auto* rf = p->maybeRpcFields();
+ LOG_ALWAYS_FATAL_IF(rf == nullptr);
+ std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>> fds;
+ if (rf->mFds) {
+ fds.reserve(rf->mFds->size());
+ for (const auto& fd : *rf->mFds) {
+ fds.push_back(binder::borrowed_fd(toRawFd(fd)));
+ }
+ }
+ status_t result =
+ rpcSetDataReference(rf->mSession, p->mData, p->mDataSize,
+ rf->mObjectPositions.data(), rf->mObjectPositions.size(),
+ std::move(fds), do_nothing_release_func);
+ LOG_ALWAYS_FATAL_IF(result != OK, "Failed: %s", statusToString(result).c_str());
+ } else {
+#ifdef BINDER_WITH_KERNEL_IPC
+ // warning: this must match the logic in ipcSetDataReference
+ auto* kf = p->maybeKernelFields();
+ LOG_ALWAYS_FATAL_IF(kf == nullptr);
+
+ // Ownership of FDs is passed to the Parcel from kernel binder. This should be refactored
+ // to move this ownership out of Parcel and into release_func. However, today, Parcel
+ // always assums it can own and close FDs today. So, for purposes of testing consistency,
+ // , create new FDs it can own.
+
+ uint8_t* newData = new uint8_t[p->mDataSize]; // deleted by delete_data_release_func
+ memcpy(newData, p->mData, p->mDataSize);
+ for (size_t i = 0; i < kf->mObjectsSize; i++) {
+ flat_binder_object* flat =
+ reinterpret_cast<flat_binder_object*>(newData + kf->mObjects[i]);
+ if (flat->hdr.type == BINDER_TYPE_FD) {
+ flat->handle = fcntl(flat->handle, F_DUPFD_CLOEXEC, 0);
+ }
+ }
+
+ ipcSetDataReference(newData, p->mDataSize, kf->mObjects, kf->mObjectsSize,
+ delete_data_release_func);
+#endif // BINDER_WITH_KERNEL_IPC
+ }
+}
+
void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
size_t objectsCount, release_func relFunc) {
// this code uses 'mOwner == nullptr' to understand whether it owns memory
@@ -2744,6 +2801,7 @@ void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, const bin
auto* kernelFields = maybeKernelFields();
LOG_ALWAYS_FATAL_IF(kernelFields == nullptr); // guaranteed by freeData.
+ // must match makeDangerousViewOf
mData = const_cast<uint8_t*>(data);
mDataSize = mDataCapacity = dataSize;
kernelFields->mObjects = const_cast<binder_size_t*>(objects);
@@ -2822,6 +2880,7 @@ status_t Parcel::rpcSetDataReference(
auto* rpcFields = maybeRpcFields();
LOG_ALWAYS_FATAL_IF(rpcFields == nullptr); // guaranteed by markForRpc.
+ // must match makeDangerousViewOf
mData = const_cast<uint8_t*>(data);
mDataSize = mDataCapacity = dataSize;
mOwner = relFunc;
@@ -2889,15 +2948,17 @@ void Parcel::releaseObjects()
#endif // BINDER_WITH_KERNEL_IPC
}
-void Parcel::acquireObjects()
-{
+void Parcel::reacquireObjects(size_t objectsSize) {
auto* kernelFields = maybeKernelFields();
if (kernelFields == nullptr) {
return;
}
#ifdef BINDER_WITH_KERNEL_IPC
- size_t i = kernelFields->mObjectsSize;
+ LOG_ALWAYS_FATAL_IF(objectsSize > kernelFields->mObjectsSize,
+ "Object size %zu out of range of %zu", objectsSize,
+ kernelFields->mObjectsSize);
+ size_t i = objectsSize;
if (i == 0) {
return;
}
@@ -2907,8 +2968,10 @@ void Parcel::acquireObjects()
while (i > 0) {
i--;
const flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(data + objects[i]);
- acquire_object(proc, *flat, this);
+ acquire_object(proc, *flat, this, false /*tagFds*/); // they are already tagged
}
+#else
+ (void) objectsSize;
#endif // BINDER_WITH_KERNEL_IPC
}
@@ -2925,7 +2988,7 @@ void Parcel::freeDataNoInit()
//ALOGI("Freeing data ref of %p (pid=%d)", this, getpid());
auto* kernelFields = maybeKernelFields();
// Close FDs before freeing, otherwise they will leak for kernel binder.
- closeFileDescriptors();
+ closeFileDescriptors(/*newObjectsSize=*/0);
mOwner(mData, mDataSize, kernelFields ? kernelFields->mObjects : nullptr,
kernelFields ? kernelFields->mObjectsSize : 0);
} else {
@@ -2953,6 +3016,14 @@ status_t Parcel::growData(size_t len)
return BAD_VALUE;
}
+ if (mDataPos > mDataSize) {
+ // b/370831157 - this case used to abort. We also don't expect mDataPos < mDataSize, but
+ // this would only waste a bit of memory, so it's okay.
+ ALOGE("growData only expected at the end of a Parcel. pos: %zu, size: %zu, capacity: %zu",
+ mDataPos, len, mDataCapacity);
+ return BAD_VALUE;
+ }
+
if (len > SIZE_MAX - mDataSize) return NO_MEMORY; // overflow
if (mDataSize + len > SIZE_MAX / 3) return NO_MEMORY; // overflow
size_t newSize = ((mDataSize+len)*3)/2;
@@ -3054,13 +3125,38 @@ status_t Parcel::continueWrite(size_t desired)
objectsSize = 0;
} else {
if (kernelFields) {
+#ifdef BINDER_WITH_KERNEL_IPC
+ validateReadData(mDataSize); // hack to sort the objects
while (objectsSize > 0) {
- if (kernelFields->mObjects[objectsSize - 1] < desired) break;
+ if (kernelFields->mObjects[objectsSize - 1] + sizeof(flat_binder_object) <=
+ desired)
+ break;
objectsSize--;
}
+#endif // BINDER_WITH_KERNEL_IPC
} else {
while (objectsSize > 0) {
- if (rpcFields->mObjectPositions[objectsSize - 1] < desired) break;
+ // Object size varies by type.
+ uint32_t pos = rpcFields->mObjectPositions[objectsSize - 1];
+ size_t size = sizeof(RpcFields::ObjectType);
+ uint32_t minObjectEnd;
+ if (__builtin_add_overflow(pos, sizeof(RpcFields::ObjectType), &minObjectEnd) ||
+ minObjectEnd > mDataSize) {
+ return BAD_VALUE;
+ }
+ const auto type = *reinterpret_cast<const RpcFields::ObjectType*>(mData + pos);
+ switch (type) {
+ case RpcFields::TYPE_BINDER_NULL:
+ break;
+ case RpcFields::TYPE_BINDER:
+ size += sizeof(uint64_t); // address
+ break;
+ case RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR:
+ size += sizeof(int32_t); // fd index
+ break;
+ }
+
+ if (pos + size <= desired) break;
objectsSize--;
}
}
@@ -3092,12 +3188,8 @@ status_t Parcel::continueWrite(size_t desired)
return NO_MEMORY;
}
- // Little hack to only acquire references on objects
- // we will be keeping.
- size_t oldObjectsSize = kernelFields->mObjectsSize;
- kernelFields->mObjectsSize = objectsSize;
- acquireObjects();
- kernelFields->mObjectsSize = oldObjectsSize;
+ // only acquire references on objects we are keeping
+ reacquireObjects(objectsSize);
}
if (rpcFields) {
if (status_t status = truncateRpcObjects(objectsSize); status != OK) {
@@ -3109,15 +3201,24 @@ status_t Parcel::continueWrite(size_t desired)
if (mData) {
memcpy(data, mData, mDataSize < desired ? mDataSize : desired);
}
+#ifdef BINDER_WITH_KERNEL_IPC
if (objects && kernelFields && kernelFields->mObjects) {
memcpy(objects, kernelFields->mObjects, objectsSize * sizeof(binder_size_t));
+ // All FDs are owned when `mOwner`, even when `cookie == 0`. When
+ // we switch to `!mOwner`, we need to explicitly mark the FDs as
+ // owned.
+ for (size_t i = 0; i < objectsSize; i++) {
+ flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(data + objects[i]);
+ if (flat->hdr.type == BINDER_TYPE_FD) {
+ flat->cookie = 1;
+ }
+ }
}
// ALOGI("Freeing data ref of %p (pid=%d)", this, getpid());
if (kernelFields) {
- // TODO(b/239222407): This seems wrong. We should only free FDs when
- // they are in a truncated section of the parcel.
- closeFileDescriptors();
+ closeFileDescriptors(objectsSize);
}
+#endif // BINDER_WITH_KERNEL_IPC
mOwner(mData, mDataSize, kernelFields ? kernelFields->mObjects : nullptr,
kernelFields ? kernelFields->mObjectsSize : 0);
mOwner = nullptr;
@@ -3244,11 +3345,19 @@ status_t Parcel::truncateRpcObjects(size_t newObjectsSize) {
}
while (rpcFields->mObjectPositions.size() > newObjectsSize) {
uint32_t pos = rpcFields->mObjectPositions.back();
- rpcFields->mObjectPositions.pop_back();
+ uint32_t minObjectEnd;
+ if (__builtin_add_overflow(pos, sizeof(RpcFields::ObjectType), &minObjectEnd) ||
+ minObjectEnd > mDataSize) {
+ return BAD_VALUE;
+ }
const auto type = *reinterpret_cast<const RpcFields::ObjectType*>(mData + pos);
if (type == RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR) {
- const auto fdIndex =
- *reinterpret_cast<const int32_t*>(mData + pos + sizeof(RpcFields::ObjectType));
+ uint32_t objectEnd;
+ if (__builtin_add_overflow(minObjectEnd, sizeof(int32_t), &objectEnd) ||
+ objectEnd > mDataSize) {
+ return BAD_VALUE;
+ }
+ const auto fdIndex = *reinterpret_cast<const int32_t*>(mData + minObjectEnd);
if (rpcFields->mFds == nullptr || fdIndex < 0 ||
static_cast<size_t>(fdIndex) >= rpcFields->mFds->size()) {
ALOGE("RPC Parcel contains invalid file descriptor index. index=%d fd_count=%zu",
@@ -3258,6 +3367,7 @@ status_t Parcel::truncateRpcObjects(size_t newObjectsSize) {
// In practice, this always removes the last element.
rpcFields->mFds->erase(rpcFields->mFds->begin() + fdIndex);
}
+ rpcFields->mObjectPositions.pop_back();
}
return OK;
}
diff --git a/libs/binder/PersistableBundle.cpp b/libs/binder/PersistableBundle.cpp
index abb6612a1c..99f9726c5e 100644
--- a/libs/binder/PersistableBundle.cpp
+++ b/libs/binder/PersistableBundle.cpp
@@ -119,6 +119,9 @@ status_t PersistableBundle::writeToParcel(Parcel* parcel) const {
}
RETURN_IF_FAILED(parcel->writeInt32(static_cast<int32_t>(length)));
parcel->setDataPosition(end_pos);
+ // write mHasIntent to be consistent with BaseBundle.writeToBundle. But it would always be
+ // false since PersistableBundle won't contain an intent.
+ RETURN_IF_FAILED(parcel->writeBool(false));
return NO_ERROR;
}
@@ -473,6 +476,8 @@ status_t PersistableBundle::readFromParcelInner(const Parcel* parcel, size_t len
}
}
}
+ // result intentional ignored since it will always be false;
+ RETURN_IF_FAILED(parcel->readBool());
return NO_ERROR;
}
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index bb45ad2ad5..993ad82b96 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -263,7 +263,6 @@ constexpr const char* const kManualInterfaces[] = {
"android.utils.IMemory",
"android.utils.IMemoryHeap",
"com.android.car.procfsinspector.IProcfsInspector",
- "com.android.internal.app.IAppOpsCallback",
"com.android.internal.app.IAppOpsService",
"com.android.internal.app.IBatteryStats",
"com.android.internal.os.IResultReceiver",
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 9ef4e694dd..f7465e2c1a 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -64,7 +64,10 @@ public:
* Returns the PID of the process which has made the current binder
* call. If not in a binder call, this will return getpid.
*
- * Warning: oneway transactions do not receive PID. Even if you expect
+ * Warning do not use this as a security identifier! PID is unreliable
+ * as it may be re-used. This should mostly be used for debugging.
+ *
+ * oneway transactions do not receive PID. Even if you expect
* a transaction to be synchronous, a misbehaving client could send it
* as an asynchronous call and result in a 0 PID here. Additionally, if
* there is a race and the calling process dies, the PID may still be
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 9cd2ae9c25..6c4c6fe573 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -172,14 +172,14 @@ public:
LIBBINDER_EXPORTED status_t write(const void* data, size_t len);
LIBBINDER_EXPORTED void* writeInplace(size_t len);
- LIBBINDER_EXPORTED status_t writeUnpadded(const void* data, size_t len);
LIBBINDER_EXPORTED status_t writeInt32(int32_t val);
LIBBINDER_EXPORTED status_t writeUint32(uint32_t val);
LIBBINDER_EXPORTED status_t writeInt64(int64_t val);
LIBBINDER_EXPORTED status_t writeUint64(uint64_t val);
LIBBINDER_EXPORTED status_t writeFloat(float val);
LIBBINDER_EXPORTED status_t writeDouble(double val);
- LIBBINDER_EXPORTED status_t writeCString(const char* str);
+ LIBBINDER_EXPORTED status_t writeCString(const char* str)
+ __attribute__((deprecated("use AIDL, writeString* instead")));
LIBBINDER_EXPORTED status_t writeString8(const String8& str);
LIBBINDER_EXPORTED status_t writeString8(const char* str, size_t len);
LIBBINDER_EXPORTED status_t writeString16(const String16& str);
@@ -435,7 +435,8 @@ public:
LIBBINDER_EXPORTED status_t readUtf8FromUtf16(std::unique_ptr<std::string>* str) const
__attribute__((deprecated("use std::optional version instead")));
- LIBBINDER_EXPORTED const char* readCString() const;
+ LIBBINDER_EXPORTED const char* readCString() const
+ __attribute__((deprecated("use AIDL, use readString*")));
LIBBINDER_EXPORTED String8 readString8() const;
LIBBINDER_EXPORTED status_t readString8(String8* pArg) const;
LIBBINDER_EXPORTED const char* readString8Inplace(size_t* outLen) const;
@@ -650,9 +651,14 @@ public:
LIBBINDER_EXPORTED void print(std::ostream& to, uint32_t flags = 0) const;
+ // This API is to quickly become a view of another Parcel, so that we can also
+ // test 'owner' paths quickly. It's extremely dangerous to use this API in
+ // practice, and you should never ever do it.
+ LIBBINDER_EXPORTED void makeDangerousViewOf(Parcel* p);
+
private:
- // Explicitly close all file descriptors in the parcel.
- void closeFileDescriptors();
+ // Close all file descriptors in the parcel at object positions >= newObjectsSize.
+ void closeFileDescriptors(size_t newObjectsSize);
// `objects` and `objectsSize` always 0 for RPC Parcels.
typedef void (*release_func)(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
@@ -665,7 +671,7 @@ private:
void ipcSetDataReference(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
size_t objectsCount, release_func relFunc);
// Takes ownership even when an error is returned.
- status_t rpcSetDataReference(
+ [[nodiscard]] status_t rpcSetDataReference(
const sp<RpcSession>& session, const uint8_t* data, size_t dataSize,
const uint32_t* objectTable, size_t objectTableSize,
std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds,
@@ -673,7 +679,7 @@ private:
status_t finishWrite(size_t len);
void releaseObjects();
- void acquireObjects();
+ void reacquireObjects(size_t objectSize);
status_t growData(size_t len);
// Clear the Parcel and set the capacity to `desired`.
// Doesn't reset the RPC session association.
diff --git a/libs/binder/include/binder/SafeInterface.h b/libs/binder/include/binder/SafeInterface.h
index bcbd14f9d4..e848385418 100644
--- a/libs/binder/include/binder/SafeInterface.h
+++ b/libs/binder/include/binder/SafeInterface.h
@@ -79,7 +79,7 @@ public:
template <typename T>
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type read(
const Parcel& parcel, sp<T>* t) const {
- *t = new T{};
+ *t = sp<T>::make();
return callParcel("read(sp<Flattenable>)", [&]() { return parcel.read(*(t->get())); });
}
template <typename T>
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index bd46c473b4..d69d318a28 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -419,6 +419,9 @@ binder_status_t AIBinder_unlinkToDeath(AIBinder* binder, AIBinder_DeathRecipient
* This can be used with higher-level system services to determine the caller's identity and check
* permissions.
*
+ * Warning do not use this as a security identifier! PID is unreliable as it may be re-used. This
+ * should mostly be used for debugging.
+ *
* Available since API level 29.
*
* \return calling uid or the current process's UID if this thread isn't processing a transaction.
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index f5df8d5c2f..2c2e2c8856 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -30,10 +30,14 @@ enum AServiceManager_AddServiceFlag : uint32_t {
* not be added with this flag for privacy concerns.
*/
ADD_SERVICE_ALLOW_ISOLATED = 1 << 0,
+ /**
+ * Allows services to dump sections according to priorities and format
+ */
ADD_SERVICE_DUMP_FLAG_PRIORITY_CRITICAL = 1 << 1,
ADD_SERVICE_DUMP_FLAG_PRIORITY_HIGH = 1 << 2,
ADD_SERVICE_DUMP_FLAG_PRIORITY_NORMAL = 1 << 3,
ADD_SERVICE_DUMP_FLAG_PRIORITY_DEFAULT = 1 << 4,
+ ADD_SERVICE_DUMP_FLAG_PROTO = 1 << 5,
// All other bits are reserved for internal usage
};
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index d6ac4acc29..14bc5d2b75 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -63,6 +63,9 @@ binder_exception_t AServiceManager_addServiceWithFlags(AIBinder* binder, const c
if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_DEFAULT) {
dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT;
}
+ if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PROTO) {
+ dumpFlags |= IServiceManager::DUMP_FLAG_PROTO;
+ }
if (dumpFlags == 0) {
dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT;
}
diff --git a/libs/binder/rust/src/state.rs b/libs/binder/rust/src/state.rs
index 145ae651d7..609334eb53 100644
--- a/libs/binder/rust/src/state.rs
+++ b/libs/binder/rust/src/state.rs
@@ -102,7 +102,10 @@ impl ThreadState {
/// dies and is replaced with another process with elevated permissions and
/// the same PID.
///
- /// Warning: oneway transactions do not receive PID. Even if you expect
+ /// Warning: do not use this as a security identifier! PID is unreliable
+ /// as it may be re-used. This should mostly be used for debugging.
+ ///
+ /// oneway transactions do not receive PID. Even if you expect
/// a transaction to be synchronous, a misbehaving client could send it
/// as a synchronous call and result in a 0 PID here. Additionally, if
/// there is a race and the calling process dies, the PID may still be
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 391a57010a..891c0a290c 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -48,6 +48,7 @@
#include <linux/sched.h>
#include <sys/epoll.h>
+#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/un.h>
@@ -112,6 +113,8 @@ enum BinderLibTestTranscationCode {
BINDER_LIB_TEST_LINK_DEATH_TRANSACTION,
BINDER_LIB_TEST_WRITE_FILE_TRANSACTION,
BINDER_LIB_TEST_WRITE_PARCEL_FILE_DESCRIPTOR_TRANSACTION,
+ BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION,
+ BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION,
BINDER_LIB_TEST_EXIT_TRANSACTION,
BINDER_LIB_TEST_DELAYED_EXIT_TRANSACTION,
BINDER_LIB_TEST_GET_PTR_SIZE_TRANSACTION,
@@ -548,6 +551,30 @@ class TestDeathRecipient : public IBinder::DeathRecipient, public BinderLibTestE
};
};
+ssize_t countFds() {
+ return std::distance(std::filesystem::directory_iterator("/proc/self/fd"),
+ std::filesystem::directory_iterator{});
+}
+
+struct FdLeakDetector {
+ int startCount;
+
+ FdLeakDetector() {
+ // This log statement is load bearing. We have to log something before
+ // counting FDs to make sure the logging system is initialized, otherwise
+ // the sockets it opens will look like a leak.
+ ALOGW("FdLeakDetector counting FDs.");
+ startCount = countFds();
+ }
+ ~FdLeakDetector() {
+ int endCount = countFds();
+ if (startCount != endCount) {
+ ADD_FAILURE() << "fd count changed (" << startCount << " -> " << endCount
+ << ") fd leak?";
+ }
+ }
+};
+
TEST_F(BinderLibTest, CannotUseBinderAfterFork) {
// EXPECT_DEATH works by forking the process
EXPECT_DEATH({ ProcessState::self(); }, "libbinder ProcessState can not be used after fork");
@@ -1182,6 +1209,100 @@ TEST_F(BinderLibTest, PassParcelFileDescriptor) {
EXPECT_EQ(0, read(read_end.get(), readbuf.data(), datasize));
}
+TEST_F(BinderLibTest, RecvOwnedFileDescriptors) {
+ FdLeakDetector fd_leak_detector;
+
+ Parcel data;
+ Parcel reply;
+ EXPECT_EQ(NO_ERROR,
+ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data,
+ &reply));
+ unique_fd a, b;
+ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a));
+ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b));
+}
+
+// Used to trigger fdsan error (b/239222407).
+TEST_F(BinderLibTest, RecvOwnedFileDescriptorsAndWriteInt) {
+ GTEST_SKIP() << "triggers fdsan false positive: b/370824489";
+
+ FdLeakDetector fd_leak_detector;
+
+ Parcel data;
+ Parcel reply;
+ EXPECT_EQ(NO_ERROR,
+ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data,
+ &reply));
+ reply.setDataPosition(reply.dataSize());
+ reply.writeInt32(0);
+ reply.setDataPosition(0);
+ unique_fd a, b;
+ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a));
+ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b));
+}
+
+// Used to trigger fdsan error (b/239222407).
+TEST_F(BinderLibTest, RecvOwnedFileDescriptorsAndTruncate) {
+ GTEST_SKIP() << "triggers fdsan false positive: b/370824489";
+
+ FdLeakDetector fd_leak_detector;
+
+ Parcel data;
+ Parcel reply;
+ EXPECT_EQ(NO_ERROR,
+ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data,
+ &reply));
+ reply.setDataSize(reply.dataSize() - sizeof(flat_binder_object));
+ unique_fd a, b;
+ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a));
+ EXPECT_EQ(BAD_TYPE, reply.readUniqueFileDescriptor(&b));
+}
+
+TEST_F(BinderLibTest, RecvUnownedFileDescriptors) {
+ FdLeakDetector fd_leak_detector;
+
+ Parcel data;
+ Parcel reply;
+ EXPECT_EQ(NO_ERROR,
+ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data,
+ &reply));
+ unique_fd a, b;
+ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a));
+ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b));
+}
+
+// Used to trigger fdsan error (b/239222407).
+TEST_F(BinderLibTest, RecvUnownedFileDescriptorsAndWriteInt) {
+ FdLeakDetector fd_leak_detector;
+
+ Parcel data;
+ Parcel reply;
+ EXPECT_EQ(NO_ERROR,
+ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data,
+ &reply));
+ reply.setDataPosition(reply.dataSize());
+ reply.writeInt32(0);
+ reply.setDataPosition(0);
+ unique_fd a, b;
+ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a));
+ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b));
+}
+
+// Used to trigger fdsan error (b/239222407).
+TEST_F(BinderLibTest, RecvUnownedFileDescriptorsAndTruncate) {
+ FdLeakDetector fd_leak_detector;
+
+ Parcel data;
+ Parcel reply;
+ EXPECT_EQ(NO_ERROR,
+ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data,
+ &reply));
+ reply.setDataSize(reply.dataSize() - sizeof(flat_binder_object));
+ unique_fd a, b;
+ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a));
+ EXPECT_EQ(BAD_TYPE, reply.readUniqueFileDescriptor(&b));
+}
+
TEST_F(BinderLibTest, PromoteLocal) {
sp<IBinder> strong = new BBinder();
wp<IBinder> weak = strong;
@@ -2261,6 +2382,40 @@ public:
if (ret != size) return UNKNOWN_ERROR;
return NO_ERROR;
}
+ case BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION: {
+ unique_fd fd1(memfd_create("memfd1", MFD_CLOEXEC));
+ if (!fd1.ok()) {
+ PLOGE("memfd_create failed");
+ return UNKNOWN_ERROR;
+ }
+ unique_fd fd2(memfd_create("memfd2", MFD_CLOEXEC));
+ if (!fd2.ok()) {
+ PLOGE("memfd_create failed");
+ return UNKNOWN_ERROR;
+ }
+ status_t ret;
+ ret = reply->writeFileDescriptor(fd1.release(), true);
+ if (ret != NO_ERROR) {
+ return ret;
+ }
+ ret = reply->writeFileDescriptor(fd2.release(), true);
+ if (ret != NO_ERROR) {
+ return ret;
+ }
+ return NO_ERROR;
+ }
+ case BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION: {
+ status_t ret;
+ ret = reply->writeFileDescriptor(STDOUT_FILENO, false);
+ if (ret != NO_ERROR) {
+ return ret;
+ }
+ ret = reply->writeFileDescriptor(STDERR_FILENO, false);
+ if (ret != NO_ERROR) {
+ return ret;
+ }
+ return NO_ERROR;
+ }
case BINDER_LIB_TEST_DELAYED_EXIT_TRANSACTION:
alarm(10);
return NO_ERROR;
diff --git a/libs/binder/tests/binderParcelUnitTest.cpp b/libs/binder/tests/binderParcelUnitTest.cpp
index 32a70e5b11..6259d9d2d2 100644
--- a/libs/binder/tests/binderParcelUnitTest.cpp
+++ b/libs/binder/tests/binderParcelUnitTest.cpp
@@ -33,6 +33,38 @@ using android::String8;
using android::binder::Status;
using android::binder::unique_fd;
+static void checkCString(const char* str) {
+ for (size_t i = 0; i < 3; i++) {
+ Parcel p;
+
+ for (size_t j = 0; j < i; j++) p.writeInt32(3);
+
+ p.writeCString(str);
+ int32_t pos = p.dataPosition();
+
+ p.setDataPosition(0);
+
+ for (size_t j = 0; j < i; j++) p.readInt32();
+ const char* str2 = p.readCString();
+
+ ASSERT_EQ(std::string(str), str2);
+ ASSERT_EQ(pos, p.dataPosition());
+ }
+}
+
+TEST(Parcel, TestReadCString) {
+ // we should remove the *CString APIs, but testing them until
+ // they are deleted.
+ checkCString("");
+ checkCString("a");
+ checkCString("\n");
+ checkCString("32");
+ checkCString("321");
+ checkCString("3210");
+ checkCString("3210b");
+ checkCString("123434");
+}
+
TEST(Parcel, NonNullTerminatedString8) {
String8 kTestString = String8("test-is-good");
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index 20b6b44c62..b2ba1ae38d 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -25,6 +25,8 @@
#include <binder/ParcelableHolder.h>
#include <binder/PersistableBundle.h>
#include <binder/Status.h>
+#include <fuzzbinder/random_binder.h>
+#include <fuzzbinder/random_fd.h>
#include <utils/Flattenable.h>
#include "../../Utils.h"
@@ -115,18 +117,15 @@ std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS {
p.setDataPosition(pos);
FUZZ_LOG() << "setDataPosition done";
},
- [] (const ::android::Parcel& p, FuzzedDataProvider& provider) {
- size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
- std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
- FUZZ_LOG() << "about to setData: " <<(bytes.data() ? HexString(bytes.data(), bytes.size()) : "null");
- // TODO: allow all read and write operations
- (*const_cast<::android::Parcel*>(&p)).setData(bytes.data(), bytes.size());
- FUZZ_LOG() << "setData done";
- },
PARCEL_READ_NO_STATUS(size_t, allowFds),
PARCEL_READ_NO_STATUS(size_t, hasFileDescriptors),
PARCEL_READ_NO_STATUS(std::vector<android::sp<android::IBinder>>, debugReadAllStrongBinders),
PARCEL_READ_NO_STATUS(std::vector<int>, debugReadAllFileDescriptors),
+ [] (const ::android::Parcel& p, FuzzedDataProvider&) {
+ FUZZ_LOG() << "about to markSensitive";
+ p.markSensitive();
+ FUZZ_LOG() << "markSensitive done";
+ },
[] (const ::android::Parcel& p, FuzzedDataProvider& provider) {
std::string interface = provider.ConsumeRandomLengthString();
FUZZ_LOG() << "about to enforceInterface: " << interface;
@@ -402,5 +401,113 @@ std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS {
FUZZ_LOG() << " toString() result: " << toString;
},
};
+
+std::vector<ParcelWrite<::android::Parcel>> BINDER_PARCEL_WRITE_FUNCTIONS {
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call setDataSize";
+ size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+ p.setDataSize(len);
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call setDataCapacity";
+ size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+ p.setDataCapacity(len);
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call setData";
+ size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+ std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
+ p.setData(bytes.data(), bytes.size());
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+ FUZZ_LOG() << "about to call appendFrom";
+
+ std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(provider.ConsumeIntegralInRange<size_t>(0, 4096));
+ ::android::Parcel p2;
+ fillRandomParcel(&p2, FuzzedDataProvider(bytes.data(), bytes.size()), options);
+
+ int32_t start = provider.ConsumeIntegral<int32_t>();
+ int32_t len = provider.ConsumeIntegral<int32_t>();
+ p.appendFrom(&p2, start, len);
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call pushAllowFds";
+ bool val = provider.ConsumeBool();
+ p.pushAllowFds(val);
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call restoreAllowFds";
+ bool val = provider.ConsumeBool();
+ p.restoreAllowFds(val);
+ },
+ // markForBinder - covered by fillRandomParcel, aborts if called multiple times
+ // markForRpc - covered by fillRandomParcel, aborts if called multiple times
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call writeInterfaceToken";
+ std::string interface = provider.ConsumeRandomLengthString();
+ p.writeInterfaceToken(android::String16(interface.c_str()));
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call setEnforceNoDataAvail";
+ p.setEnforceNoDataAvail(provider.ConsumeBool());
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call setServiceFuzzing";
+ p.setServiceFuzzing();
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call freeData";
+ p.freeData();
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call write";
+ size_t len = provider.ConsumeIntegralInRange<size_t>(0, 256);
+ std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
+ p.write(bytes.data(), bytes.size());
+ },
+ // write* - write functions all implemented by calling 'write' itself.
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+ FUZZ_LOG() << "about to call writeStrongBinder";
+
+ // TODO: this logic is somewhat duplicated with random parcel
+ android::sp<android::IBinder> binder;
+ if (provider.ConsumeBool() && options->extraBinders.size() > 0) {
+ binder = options->extraBinders.at(
+ provider.ConsumeIntegralInRange<size_t>(0, options->extraBinders.size() - 1));
+ } else {
+ binder = android::getRandomBinder(&provider);
+ options->extraBinders.push_back(binder);
+ }
+
+ p.writeStrongBinder(binder);
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call writeFileDescriptor (no ownership)";
+ p.writeFileDescriptor(STDERR_FILENO, false /* takeOwnership */);
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+ FUZZ_LOG() << "about to call writeFileDescriptor (take ownership)";
+ std::vector<unique_fd> fds = android::getRandomFds(&provider);
+ if (fds.size() == 0) return;
+
+ p.writeDupFileDescriptor(fds.at(0).get());
+ options->extraFds.insert(options->extraFds.end(),
+ std::make_move_iterator(fds.begin() + 1),
+ std::make_move_iterator(fds.end()));
+ },
+ // TODO: writeBlob
+ // TODO: writeDupImmutableBlobFileDescriptor
+ // TODO: writeObject (or make the API private more likely)
+ [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call writeNoException";
+ p.writeNoException();
+ },
+ [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call replaceCallingWorkSourceUid";
+ uid_t uid = provider.ConsumeIntegral<uid_t>();
+ p.replaceCallingWorkSourceUid(uid);
+ },
+};
+
// clang-format on
#pragma clang diagnostic pop
diff --git a/libs/binder/tests/parcel_fuzzer/binder.h b/libs/binder/tests/parcel_fuzzer/binder.h
index 0c51d68a37..b0ac140d3f 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.h
+++ b/libs/binder/tests/parcel_fuzzer/binder.h
@@ -21,3 +21,4 @@
#include "parcel_fuzzer.h"
extern std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS;
+extern std::vector<ParcelWrite<::android::Parcel>> BINDER_PARCEL_WRITE_FUNCTIONS;
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
index e3a337171f..3f8d71d1f9 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
@@ -20,8 +20,11 @@
#include "aidl/parcelables/GenericDataParcelable.h"
#include "aidl/parcelables/SingleDataParcelable.h"
+#include <android/binder_libbinder.h>
#include <android/binder_parcel_utils.h>
#include <android/binder_parcelable_utils.h>
+#include <fuzzbinder/random_binder.h>
+#include <fuzzbinder/random_fd.h>
#include "util.h"
@@ -211,16 +214,51 @@ std::vector<ParcelRead<NdkParcelAdapter>> BINDER_NDK_PARCEL_READ_FUNCTIONS{
binder_status_t status = AParcel_marshal(p.aParcel(), buffer, start, len);
FUZZ_LOG() << "status: " << status;
},
- [](const NdkParcelAdapter& /*p*/, FuzzedDataProvider& provider) {
- FUZZ_LOG() << "about to unmarshal AParcel";
+};
+std::vector<ParcelWrite<NdkParcelAdapter>> BINDER_NDK_PARCEL_WRITE_FUNCTIONS{
+ [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+ FUZZ_LOG() << "about to call AParcel_writeStrongBinder";
+
+ // TODO: this logic is somewhat duplicated with random parcel
+ android::sp<android::IBinder> binder;
+ if (provider.ConsumeBool() && options->extraBinders.size() > 0) {
+ binder = options->extraBinders.at(
+ provider.ConsumeIntegralInRange<size_t>(0, options->extraBinders.size() - 1));
+ } else {
+ binder = android::getRandomBinder(&provider);
+ options->extraBinders.push_back(binder);
+ }
+
+ ndk::SpAIBinder abinder = ndk::SpAIBinder(AIBinder_fromPlatformBinder(binder));
+ AParcel_writeStrongBinder(p.aParcel(), abinder.get());
+ },
+ [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+ FUZZ_LOG() << "about to call AParcel_writeParcelFileDescriptor";
+
+ auto fds = android::getRandomFds(&provider);
+ if (fds.size() == 0) return;
+
+ AParcel_writeParcelFileDescriptor(p.aParcel(), fds.at(0).get());
+ options->extraFds.insert(options->extraFds.end(),
+ std::make_move_iterator(fds.begin() + 1),
+ std::make_move_iterator(fds.end()));
+ },
+ // all possible data writes can be done as a series of 4-byte reads
+ [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call AParcel_writeInt32";
+ int32_t val = provider.ConsumeIntegral<int32_t>();
+ AParcel_writeInt32(p.aParcel(), val);
+ },
+ [] (NdkParcelAdapter& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call AParcel_reset";
+ AParcel_reset(p.aParcel());
+ },
+ [](NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+ FUZZ_LOG() << "about to call AParcel_unmarshal";
size_t len = provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes());
- std::vector<uint8_t> parcelData = provider.ConsumeBytes<uint8_t>(len);
- const uint8_t* buffer = parcelData.data();
- const size_t bufferLen = parcelData.size();
- NdkParcelAdapter adapter;
- binder_status_t status = AParcel_unmarshal(adapter.aParcel(), buffer, bufferLen);
+ std::vector<uint8_t> data = provider.ConsumeBytes<uint8_t>(len);
+ binder_status_t status = AParcel_unmarshal(p.aParcel(), data.data(), data.size());
FUZZ_LOG() << "status: " << status;
},
-
};
// clang-format on
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.h b/libs/binder/tests/parcel_fuzzer/binder_ndk.h
index d19f25bc88..0c8b725400 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.h
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.h
@@ -50,3 +50,4 @@ private:
};
extern std::vector<ParcelRead<NdkParcelAdapter>> BINDER_NDK_PARCEL_READ_FUNCTIONS;
+extern std::vector<ParcelWrite<NdkParcelAdapter>> BINDER_NDK_PARCEL_WRITE_FUNCTIONS;
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
index 2812da79fa..11fcb061f6 100644
--- a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
@@ -28,6 +28,9 @@ struct RandomParcelOptions {
std::function<void(Parcel* p, FuzzedDataProvider& provider)> writeHeader;
std::vector<sp<IBinder>> extraBinders;
std::vector<binder::unique_fd> extraFds;
+
+ // internal state owned by fillRandomParcel, for Parcel views
+ std::vector<std::unique_ptr<Parcel>> extraParcels;
};
/**
diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp
index a57d07fb24..d06b2d9020 100644
--- a/libs/binder/tests/parcel_fuzzer/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/main.cpp
@@ -70,7 +70,7 @@ void doTransactFuzz(const char* backend, const sp<B>& binder, FuzzedDataProvider
uint32_t code = provider.ConsumeIntegral<uint32_t>();
uint32_t flag = provider.ConsumeIntegral<uint32_t>();
- FUZZ_LOG() << "backend: " << backend;
+ FUZZ_LOG() << "doTransactFuzz backend: " << backend;
RandomParcelOptions options;
@@ -80,6 +80,7 @@ void doTransactFuzz(const char* backend, const sp<B>& binder, FuzzedDataProvider
(void)binder->transact(code, data, &reply, flag);
}
+// start with a Parcel full of data (e.g. like you get from another process)
template <typename P>
void doReadFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
FuzzedDataProvider&& provider) {
@@ -95,12 +96,12 @@ void doReadFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
RandomParcelOptions options;
P p;
- fillRandomParcel(&p, std::move(provider), &options);
+ fillRandomParcel(&p, std::move(provider), &options); // consumes provider
// since we are only using a byte to index
- CHECK(reads.size() <= 255) << reads.size();
+ CHECK_LE(reads.size(), 255u) << reads.size();
- FUZZ_LOG() << "backend: " << backend;
+ FUZZ_LOG() << "doReadFuzz backend: " << backend;
FUZZ_LOG() << "input: " << HexString(p.data(), p.dataSize());
FUZZ_LOG() << "instructions: " << HexString(instructions.data(), instructions.size());
@@ -115,26 +116,34 @@ void doReadFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
}
}
-// Append two random parcels.
template <typename P>
-void doAppendFuzz(const char* backend, FuzzedDataProvider&& provider) {
- int32_t start = provider.ConsumeIntegral<int32_t>();
- int32_t len = provider.ConsumeIntegral<int32_t>();
+void doReadWriteFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
+ const std::vector<ParcelWrite<P>>& writes, FuzzedDataProvider&& provider) {
+ RandomParcelOptions options;
+ P p;
- std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(
- provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes()));
+ // small amount of initial Parcel data, since fillRandomParcel uses makeDangerousViewOf
+ std::vector<uint8_t> parcelData =
+ provider.ConsumeBytes<uint8_t>(provider.ConsumeIntegralInRange<size_t>(0, 20));
+ fillRandomParcel(&p, FuzzedDataProvider(parcelData.data(), parcelData.size()), &options);
- // same options so that FDs and binders could be shared in both Parcels
- RandomParcelOptions options;
+ // since we are only using a byte to index
+ CHECK_LE(reads.size() + writes.size(), 255u) << reads.size();
- P p0, p1;
- fillRandomParcel(&p0, FuzzedDataProvider(bytes.data(), bytes.size()), &options);
- fillRandomParcel(&p1, std::move(provider), &options);
+ FUZZ_LOG() << "doReadWriteFuzz backend: " << backend;
- FUZZ_LOG() << "backend: " << backend;
- FUZZ_LOG() << "start: " << start << " len: " << len;
+ while (provider.remaining_bytes() > 0) {
+ uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, reads.size() + writes.size() - 1);
- p0.appendFrom(&p1, start, len);
+ FUZZ_LOG() << "Instruction " << idx << " avail: " << p.dataAvail()
+ << " pos: " << p.dataPosition() << " cap: " << p.dataCapacity();
+
+ if (idx < reads.size()) {
+ reads.at(idx)(p, provider);
+ } else {
+ writes.at(idx - reads.size())(p, provider, &options);
+ }
+ }
}
void* NothingClass_onCreate(void* args) {
@@ -156,7 +165,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
FuzzedDataProvider provider = FuzzedDataProvider(data, size);
- const std::function<void(FuzzedDataProvider &&)> fuzzBackend[] = {
+ const std::function<void(FuzzedDataProvider&&)> fuzzBackend[] = {
[](FuzzedDataProvider&& provider) {
doTransactFuzz<
::android::hardware::Parcel>("hwbinder",
@@ -187,10 +196,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
std::move(provider));
},
[](FuzzedDataProvider&& provider) {
- doAppendFuzz<::android::Parcel>("binder", std::move(provider));
+ doReadWriteFuzz<::android::Parcel>("binder", BINDER_PARCEL_READ_FUNCTIONS,
+ BINDER_PARCEL_WRITE_FUNCTIONS,
+ std::move(provider));
},
[](FuzzedDataProvider&& provider) {
- doAppendFuzz<NdkParcelAdapter>("binder_ndk", std::move(provider));
+ doReadWriteFuzz<NdkParcelAdapter>("binder_ndk", BINDER_NDK_PARCEL_READ_FUNCTIONS,
+ BINDER_NDK_PARCEL_WRITE_FUNCTIONS,
+ std::move(provider));
},
};
diff --git a/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h b/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h
index 765a93e8c9..dbd0caea01 100644
--- a/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h
+++ b/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h
@@ -15,9 +15,13 @@
*/
#pragma once
+#include <fuzzbinder/random_parcel.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <functional>
template <typename P>
using ParcelRead = std::function<void(const P& p, FuzzedDataProvider& provider)>;
+template <typename P>
+using ParcelWrite = std::function<void(P& p, FuzzedDataProvider& provider,
+ android::RandomParcelOptions* options)>;
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
index 7c196142e8..61b9612ba5 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
@@ -17,6 +17,7 @@
#include <fuzzbinder/random_parcel.h>
#include <android-base/logging.h>
+#include <binder/Functional.h>
#include <binder/RpcSession.h>
#include <binder/RpcTransportRaw.h>
#include <fuzzbinder/random_binder.h>
@@ -32,10 +33,39 @@ static void fillRandomParcelData(Parcel* p, FuzzedDataProvider&& provider) {
CHECK(OK == p->write(data.data(), data.size()));
}
-void fillRandomParcel(Parcel* p, FuzzedDataProvider&& provider, RandomParcelOptions* options) {
+void fillRandomParcel(Parcel* outputParcel, FuzzedDataProvider&& provider,
+ RandomParcelOptions* options) {
CHECK_NE(options, nullptr);
- if (provider.ConsumeBool()) {
+ const uint8_t fuzzerParcelOptions = provider.ConsumeIntegral<uint8_t>();
+ const bool resultShouldBeView = fuzzerParcelOptions & 1;
+ const bool resultShouldBeRpc = fuzzerParcelOptions & 2;
+ const bool resultShouldMarkSensitive = fuzzerParcelOptions & 4;
+
+ auto sensitivity_guard = binder::impl::make_scope_guard([&]() {
+ if (resultShouldMarkSensitive) {
+ outputParcel->markSensitive();
+ }
+ });
+
+ Parcel* p;
+ if (resultShouldBeView) {
+ options->extraParcels.push_back(std::make_unique<Parcel>());
+ // held for duration of test, so that view will be valid
+ p = options->extraParcels[options->extraParcels.size() - 1].get();
+ } else {
+ p = outputParcel; // directly fill out the output Parcel
+ }
+
+ // must be last guard, so outputParcel gets setup as view before
+ // other guards
+ auto viewify_guard = binder::impl::make_scope_guard([&]() {
+ if (resultShouldBeView) {
+ outputParcel->makeDangerousViewOf(p);
+ }
+ });
+
+ if (resultShouldBeRpc) {
auto session = RpcSession::make(RpcTransportCtxFactoryRaw::make());
CHECK_EQ(OK, session->addNullDebuggingClient());
// Set the protocol version so that we don't crash if the session
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
index 0ed8a554ac..3cb628925c 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
@@ -281,9 +281,9 @@ void generateSeedsFromRecording(borrowed_fd fd,
// This buffer holds the bytes which will be used for fillRandomParcel API
std::vector<uint8_t> fillParcelBuffer;
- // Don't take rpc path
- uint8_t rpcBranch = 0;
- impl::writeReversedBuffer(fillParcelBuffer, rpcBranch);
+ // Use all default options.
+ uint8_t parcelOptions = 0;
+ impl::writeReversedBuffer(fillParcelBuffer, parcelOptions);
// Implicit branch on this path -> options->writeHeader(p, provider)
uint8_t writeHeaderInternal = 0;
diff --git a/libs/debugstore/rust/src/core.rs b/libs/debugstore/rust/src/core.rs
index 6bf79d4e57..16147dd092 100644
--- a/libs/debugstore/rust/src/core.rs
+++ b/libs/debugstore/rust/src/core.rs
@@ -48,7 +48,7 @@ impl DebugStore {
///
/// This constant is used as a part of the debug store's data format,
/// allowing for version tracking and compatibility checks.
- const ENCODE_VERSION: u32 = 1;
+ const ENCODE_VERSION: u32 = 3;
/// Creates a new instance of `DebugStore` with specified event limit and maximum delay.
fn new() -> Self {
@@ -123,20 +123,25 @@ impl DebugStore {
impl fmt::Display for DebugStore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // Write the debug store header information
let uptime_now = uptimeMillis();
write!(f, "{},{},{}::", Self::ENCODE_VERSION, self.event_store.len(), uptime_now)?;
+ // Join events with a separator
write!(
f,
"{}",
- self.event_store.fold(String::new(), |mut acc, event| {
+ self.event_store.rfold(String::new(), |mut acc, event| {
if !acc.is_empty() {
acc.push_str("||");
}
acc.push_str(&event.to_string());
acc
})
- )
+ )?;
+
+ // Write the debug store footer
+ write!(f, ";;")
}
}
diff --git a/libs/debugstore/rust/src/storage.rs b/libs/debugstore/rust/src/storage.rs
index 2ad7f4e0b4..47760f3f21 100644
--- a/libs/debugstore/rust/src/storage.rs
+++ b/libs/debugstore/rust/src/storage.rs
@@ -32,14 +32,18 @@ impl<T, const N: usize> Storage<T, N> {
self.insertion_buffer.force_push(value);
}
- /// Folds over the elements in the storage using the provided function.
- pub fn fold<U, F>(&self, init: U, mut func: F) -> U
+ /// Folds over the elements in the storage in reverse order using the provided function.
+ pub fn rfold<U, F>(&self, init: U, mut func: F) -> U
where
F: FnMut(U, &T) -> U,
{
- let mut acc = init;
+ let mut items = Vec::new();
while let Some(value) = self.insertion_buffer.pop() {
- acc = func(acc, &value);
+ items.push(value);
+ }
+ let mut acc = init;
+ for value in items.iter().rev() {
+ acc = func(acc, value);
}
acc
}
@@ -59,18 +63,18 @@ mod tests {
let storage = Storage::<i32, 10>::new();
storage.insert(7);
- let sum = storage.fold(0, |acc, &x| acc + x);
+ let sum = storage.rfold(0, |acc, &x| acc + x);
assert_eq!(sum, 7, "The sum of the elements should be equal to the inserted value.");
}
#[test]
- fn test_fold_functionality() {
+ fn test_rfold_functionality() {
let storage = Storage::<i32, 5>::new();
storage.insert(1);
storage.insert(2);
storage.insert(3);
- let sum = storage.fold(0, |acc, &x| acc + x);
+ let sum = storage.rfold(0, |acc, &x| acc + x);
assert_eq!(
sum, 6,
"The sum of the elements should be equal to the sum of inserted values."
@@ -84,13 +88,13 @@ mod tests {
storage.insert(2);
storage.insert(5);
- let first_sum = storage.fold(0, |acc, &x| acc + x);
+ let first_sum = storage.rfold(0, |acc, &x| acc + x);
assert_eq!(first_sum, 8, "The sum of the elements should be equal to the inserted values.");
storage.insert(30);
storage.insert(22);
- let second_sum = storage.fold(0, |acc, &x| acc + x);
+ let second_sum = storage.rfold(0, |acc, &x| acc + x);
assert_eq!(
second_sum, 52,
"The sum of the elements should be equal to the inserted values."
@@ -103,7 +107,7 @@ mod tests {
storage.insert(1);
// This value should overwrite the previously inserted value (1).
storage.insert(4);
- let sum = storage.fold(0, |acc, &x| acc + x);
+ let sum = storage.rfold(0, |acc, &x| acc + x);
assert_eq!(sum, 4, "The sum of the elements should be equal to the inserted values.");
}
@@ -128,7 +132,24 @@ mod tests {
thread.join().expect("Thread should finish without panicking");
}
- let count = storage.fold(0, |acc, _| acc + 1);
+ let count = storage.rfold(0, |acc, _| acc + 1);
assert_eq!(count, 100, "Storage should be filled to its limit with concurrent insertions.");
}
+
+ #[test]
+ fn test_rfold_order() {
+ let storage = Storage::<i32, 5>::new();
+ storage.insert(1);
+ storage.insert(2);
+ storage.insert(3);
+
+ let mut result = Vec::new();
+ storage.rfold((), |_, &x| result.push(x));
+
+ assert_eq!(
+ result,
+ vec![3, 2, 1],
+ "Elements should be processed in reverse order of insertion"
+ );
+ }
}
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index 368f5e079c..5244442ff3 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -21,10 +21,12 @@ cc_test {
"enum_test.cpp",
"expected_test.cpp",
"fake_guard_test.cpp",
+ "finalizer_test.cpp",
"flags_test.cpp",
"function_test.cpp",
"future_test.cpp",
"hash_test.cpp",
+ "ignore_test.cpp",
"match_test.cpp",
"mixins_test.cpp",
"non_null_test.cpp",
diff --git a/libs/ftl/finalizer_test.cpp b/libs/ftl/finalizer_test.cpp
new file mode 100644
index 0000000000..4f5c2258db
--- /dev/null
+++ b/libs/ftl/finalizer_test.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2024 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 <memory>
+#include <type_traits>
+#include <utility>
+
+#include <ftl/finalizer.h>
+#include <gtest/gtest.h>
+
+namespace android::test {
+
+namespace {
+
+struct Counter {
+ constexpr auto increment_fn() {
+ return [this] { ++value_; };
+ }
+
+ auto increment_finalizer() {
+ return ftl::Finalizer([this] { ++value_; });
+ }
+
+ [[nodiscard]] constexpr auto value() const -> int { return value_; }
+
+ private:
+ int value_ = 0;
+};
+
+struct CounterPair {
+ constexpr auto increment_first_fn() { return first.increment_fn(); }
+ constexpr auto increment_second_fn() { return second.increment_fn(); }
+ [[nodiscard]] constexpr auto values() const -> std::pair<int, int> {
+ return {first.value(), second.value()};
+ }
+
+ private:
+ Counter first;
+ Counter second;
+};
+
+} // namespace
+
+TEST(Finalizer, DefaultConstructionAndNoOpDestructionWhenPolymorphicType) {
+ ftl::FinalizerStd finalizer1;
+ ftl::FinalizerFtl finalizer2;
+ ftl::FinalizerFtl1 finalizer3;
+ ftl::FinalizerFtl2 finalizer4;
+ ftl::FinalizerFtl3 finalizer5;
+}
+
+TEST(Finalizer, InvokesTheFunctionOnDestruction) {
+ Counter counter;
+ {
+ const auto finalizer = counter.increment_finalizer();
+ EXPECT_EQ(counter.value(), 0);
+ }
+ EXPECT_EQ(counter.value(), 1);
+}
+
+TEST(Finalizer, InvocationCanBeCanceled) {
+ Counter counter;
+ {
+ auto finalizer = counter.increment_finalizer();
+ EXPECT_EQ(counter.value(), 0);
+ finalizer.cancel();
+ EXPECT_EQ(counter.value(), 0);
+ }
+ EXPECT_EQ(counter.value(), 0);
+}
+
+TEST(Finalizer, InvokesTheFunctionOnce) {
+ Counter counter;
+ {
+ auto finalizer = counter.increment_finalizer();
+ EXPECT_EQ(counter.value(), 0);
+ finalizer();
+ EXPECT_EQ(counter.value(), 1);
+ finalizer();
+ EXPECT_EQ(counter.value(), 1);
+ }
+ EXPECT_EQ(counter.value(), 1);
+}
+
+TEST(Finalizer, SelfInvocationIsAllowedAndANoOp) {
+ Counter counter;
+ ftl::FinalizerStd finalizer;
+ finalizer = ftl::Finalizer([&]() {
+ counter.increment_fn()();
+ finalizer(); // recursive invocation should do nothing.
+ });
+ EXPECT_EQ(counter.value(), 0);
+ finalizer();
+ EXPECT_EQ(counter.value(), 1);
+}
+
+TEST(Finalizer, MoveConstruction) {
+ Counter counter;
+ {
+ ftl::FinalizerStd outer_finalizer = counter.increment_finalizer();
+ EXPECT_EQ(counter.value(), 0);
+ {
+ ftl::FinalizerStd inner_finalizer = std::move(outer_finalizer);
+ static_assert(std::is_same_v<decltype(inner_finalizer), decltype(outer_finalizer)>);
+ EXPECT_EQ(counter.value(), 0);
+ }
+ EXPECT_EQ(counter.value(), 1);
+ }
+ EXPECT_EQ(counter.value(), 1);
+}
+
+TEST(Finalizer, MoveConstructionWithImplicitConversion) {
+ Counter counter;
+ {
+ auto outer_finalizer = counter.increment_finalizer();
+ EXPECT_EQ(counter.value(), 0);
+ {
+ ftl::FinalizerStd inner_finalizer = std::move(outer_finalizer);
+ static_assert(!std::is_same_v<decltype(inner_finalizer), decltype(outer_finalizer)>);
+ EXPECT_EQ(counter.value(), 0);
+ }
+ EXPECT_EQ(counter.value(), 1);
+ }
+ EXPECT_EQ(counter.value(), 1);
+}
+
+TEST(Finalizer, MoveAssignment) {
+ CounterPair pair;
+ {
+ ftl::FinalizerStd outer_finalizer = ftl::Finalizer(pair.increment_first_fn());
+ EXPECT_EQ(pair.values(), std::make_pair(0, 0));
+
+ {
+ ftl::FinalizerStd inner_finalizer = ftl::Finalizer(pair.increment_second_fn());
+ static_assert(std::is_same_v<decltype(inner_finalizer), decltype(outer_finalizer)>);
+ EXPECT_EQ(pair.values(), std::make_pair(0, 0));
+ inner_finalizer = std::move(outer_finalizer);
+ EXPECT_EQ(pair.values(), std::make_pair(0, 1));
+ }
+ EXPECT_EQ(pair.values(), std::make_pair(1, 1));
+ }
+ EXPECT_EQ(pair.values(), std::make_pair(1, 1));
+}
+
+TEST(Finalizer, MoveAssignmentWithImplicitConversion) {
+ CounterPair pair;
+ {
+ auto outer_finalizer = ftl::Finalizer(pair.increment_first_fn());
+ EXPECT_EQ(pair.values(), std::make_pair(0, 0));
+
+ {
+ ftl::FinalizerStd inner_finalizer = ftl::Finalizer(pair.increment_second_fn());
+ static_assert(!std::is_same_v<decltype(inner_finalizer), decltype(outer_finalizer)>);
+ EXPECT_EQ(pair.values(), std::make_pair(0, 0));
+ inner_finalizer = std::move(outer_finalizer);
+ EXPECT_EQ(pair.values(), std::make_pair(0, 1));
+ }
+ EXPECT_EQ(pair.values(), std::make_pair(1, 1));
+ }
+ EXPECT_EQ(pair.values(), std::make_pair(1, 1));
+}
+
+TEST(Finalizer, NullifiesTheFunctionWhenInvokedIfPossible) {
+ auto shared = std::make_shared<int>(0);
+ std::weak_ptr<int> weak = shared;
+
+ int count = 0;
+ {
+ auto lambda = [capture = std::move(shared)]() {};
+ auto finalizer = ftl::Finalizer(std::move(lambda));
+ EXPECT_FALSE(weak.expired());
+
+ // A lambda is not nullable. Invoking the finalizer cannot destroy it to destroy the lambda's
+ // capture.
+ finalizer();
+ EXPECT_FALSE(weak.expired());
+ }
+ // The lambda is only destroyed when the finalizer instance is destroyed.
+ EXPECT_TRUE(weak.expired());
+
+ shared = std::make_shared<int>(0);
+ weak = shared;
+
+ {
+ auto lambda = [capture = std::move(shared)]() {};
+ auto finalizer = ftl::FinalizerStd(std::move(lambda));
+ EXPECT_FALSE(weak.expired());
+
+ // Since std::function is used, and is nullable, invoking the finalizer will destroy the
+ // contained function, which will destroy the lambda's capture.
+ finalizer();
+ EXPECT_TRUE(weak.expired());
+ }
+}
+
+} // namespace android::test
diff --git a/libs/ftl/flags_test.cpp b/libs/ftl/flags_test.cpp
index 1279d1147d..bb43e8d2a3 100644
--- a/libs/ftl/flags_test.cpp
+++ b/libs/ftl/flags_test.cpp
@@ -17,7 +17,7 @@
#include <ftl/flags.h>
#include <gtest/gtest.h>
-#include <type_traits>
+#include <initializer_list>
namespace android::test {
@@ -59,6 +59,18 @@ TEST(Flags, All) {
ASSERT_FALSE(flags.all(TestFlags::ONE | TestFlags::TWO | TestFlags::THREE));
}
+TEST(Flags, ImplicitConstructionAndAssignmentFromInitializerList) {
+ Flags<TestFlags> flags = {TestFlags::ONE, TestFlags::THREE};
+ ASSERT_TRUE(flags.test(TestFlags::ONE));
+ ASSERT_FALSE(flags.test(TestFlags::TWO));
+ ASSERT_TRUE(flags.test(TestFlags::THREE));
+
+ flags = {};
+ ASSERT_FALSE(flags.test(TestFlags::ONE));
+ ASSERT_FALSE(flags.test(TestFlags::TWO));
+ ASSERT_FALSE(flags.test(TestFlags::THREE));
+}
+
TEST(Flags, DefaultConstructor_hasNoFlagsSet) {
Flags<TestFlags> flags;
ASSERT_FALSE(flags.any(TestFlags::ONE | TestFlags::TWO | TestFlags::THREE));
diff --git a/libs/ftl/ignore_test.cpp b/libs/ftl/ignore_test.cpp
new file mode 100644
index 0000000000..5d5c67b8b1
--- /dev/null
+++ b/libs/ftl/ignore_test.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2025 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 <string>
+
+#include <ftl/ignore.h>
+#include <gtest/gtest.h>
+
+namespace android::test {
+namespace {
+
+// Keep in sync with the example usage in the header file.
+
+void ftl_ignore_multiple(int arg1, const char* arg2, std::string arg3) {
+ // When invoked, all the arguments are ignored.
+ ftl::ignore(arg1, arg2, arg3);
+}
+
+void ftl_ignore_single(int arg) {
+ // It can be used like std::ignore to ignore a single value
+ ftl::ignore = arg;
+}
+
+} // namespace
+
+TEST(Ignore, Example) {
+ // The real example test is that there are no compiler warnings for unused arguments above.
+
+ // Use the example functions to avoid a compiler warning about unused functions.
+ ftl_ignore_multiple(0, "a", "b");
+ ftl_ignore_single(0);
+}
+
+} // namespace android::test
diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp
index 367b398915..457237c35e 100644
--- a/libs/ftl/non_null_test.cpp
+++ b/libs/ftl/non_null_test.cpp
@@ -81,6 +81,31 @@ static_assert(static_cast<bool>(kApplePtr));
static_assert(std::is_same_v<decltype(ftl::as_non_null(std::declval<const int* const>())),
ftl::NonNull<const int*>>);
+class Base {};
+class Derived : public Base {};
+
+static_assert(std::is_constructible_v<ftl::NonNull<void*>, ftl::NonNull<int*>>);
+static_assert(!std::is_constructible_v<ftl::NonNull<int*>, ftl::NonNull<void*>>);
+static_assert(std::is_constructible_v<ftl::NonNull<const int*>, ftl::NonNull<int*>>);
+static_assert(!std::is_constructible_v<ftl::NonNull<int*>, ftl::NonNull<const int*>>);
+static_assert(std::is_constructible_v<ftl::NonNull<Base*>, ftl::NonNull<Derived*>>);
+static_assert(!std::is_constructible_v<ftl::NonNull<Derived*>, ftl::NonNull<Base*>>);
+static_assert(std::is_constructible_v<ftl::NonNull<std::unique_ptr<const int>>,
+ ftl::NonNull<std::unique_ptr<int>>>);
+static_assert(std::is_constructible_v<ftl::NonNull<std::unique_ptr<Base>>,
+ ftl::NonNull<std::unique_ptr<Derived>>>);
+
+static_assert(std::is_assignable_v<ftl::NonNull<void*>, ftl::NonNull<int*>>);
+static_assert(!std::is_assignable_v<ftl::NonNull<int*>, ftl::NonNull<void*>>);
+static_assert(std::is_assignable_v<ftl::NonNull<const int*>, ftl::NonNull<int*>>);
+static_assert(!std::is_assignable_v<ftl::NonNull<int*>, ftl::NonNull<const int*>>);
+static_assert(std::is_assignable_v<ftl::NonNull<Base*>, ftl::NonNull<Derived*>>);
+static_assert(!std::is_assignable_v<ftl::NonNull<Derived*>, ftl::NonNull<Base*>>);
+static_assert(std::is_assignable_v<ftl::NonNull<std::unique_ptr<const int>>,
+ ftl::NonNull<std::unique_ptr<int>>>);
+static_assert(std::is_assignable_v<ftl::NonNull<std::unique_ptr<Base>>,
+ ftl::NonNull<std::unique_ptr<Derived>>>);
+
} // namespace
TEST(NonNull, SwapRawPtr) {
@@ -156,4 +181,14 @@ TEST(NonNull, UnorderedSetOfSmartPtr) {
EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
}
+TEST(NonNull, ImplicitConversion) {
+ int i = 123;
+ int j = 345;
+ auto ip = ftl::as_non_null(&i);
+ ftl::NonNull<void*> vp{ip};
+ EXPECT_EQ(vp.get(), &i);
+ vp = ftl::as_non_null(&j);
+ EXPECT_EQ(vp.get(), &j);
+}
+
} // namespace android::test
diff --git a/libs/graphicsenv/Android.bp b/libs/graphicsenv/Android.bp
index af50a2980c..dce7778229 100644
--- a/libs/graphicsenv/Android.bp
+++ b/libs/graphicsenv/Android.bp
@@ -21,10 +21,27 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
+aconfig_declarations {
+ name: "graphicsenv_flags",
+ package: "com.android.graphics.graphicsenv.flags",
+ container: "system",
+ srcs: ["graphicsenv_flags.aconfig"],
+}
+
+cc_aconfig_library {
+ name: "graphicsenv_flags_c_lib",
+ aconfig_declarations: "graphicsenv_flags",
+}
+
cc_library_shared {
name: "libgraphicsenv",
+ defaults: [
+ "aconfig_lib_cc_static_link.defaults",
+ ],
+
srcs: [
+ "FeatureOverrides.cpp",
"GpuStatsInfo.cpp",
"GraphicsEnv.cpp",
"IGpuService.cpp",
@@ -35,6 +52,10 @@ cc_library_shared {
"-Werror",
],
+ static_libs: [
+ "graphicsenv_flags_c_lib",
+ ],
+
shared_libs: [
"libbase",
"libbinder",
@@ -42,6 +63,7 @@ cc_library_shared {
"libdl_android",
"liblog",
"libutils",
+ "server_configurable_flags",
],
header_libs: [
diff --git a/libs/graphicsenv/FeatureOverrides.cpp b/libs/graphicsenv/FeatureOverrides.cpp
new file mode 100644
index 0000000000..9e7a4cf4a1
--- /dev/null
+++ b/libs/graphicsenv/FeatureOverrides.cpp
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2025 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 <cinttypes>
+
+#include <android-base/stringprintf.h>
+#include <binder/Parcel.h>
+#include <graphicsenv/FeatureOverrides.h>
+
+namespace android {
+
+using base::StringAppendF;
+
+status_t FeatureConfig::writeToParcel(Parcel* parcel) const {
+ status_t status;
+
+ status = parcel->writeUtf8AsUtf16(mFeatureName);
+ if (status != OK) {
+ return status;
+ }
+ status = parcel->writeBool(mEnabled);
+ if (status != OK) {
+ return status;
+ }
+ // Number of GPU vendor IDs.
+ status = parcel->writeVectorSize(mGpuVendorIDs);
+ if (status != OK) {
+ return status;
+ }
+ // GPU vendor IDs.
+ for (const auto& vendorID : mGpuVendorIDs) {
+ status = parcel->writeUint32(vendorID);
+ if (status != OK) {
+ return status;
+ }
+ }
+
+ return OK;
+}
+
+status_t FeatureConfig::readFromParcel(const Parcel* parcel) {
+ status_t status;
+
+ status = parcel->readUtf8FromUtf16(&mFeatureName);
+ if (status != OK) {
+ return status;
+ }
+ status = parcel->readBool(&mEnabled);
+ if (status != OK) {
+ return status;
+ }
+ // Number of GPU vendor IDs.
+ int numGpuVendorIDs;
+ status = parcel->readInt32(&numGpuVendorIDs);
+ if (status != OK) {
+ return status;
+ }
+ // GPU vendor IDs.
+ for (int i = 0; i < numGpuVendorIDs; i++) {
+ uint32_t gpuVendorIdUint;
+ status = parcel->readUint32(&gpuVendorIdUint);
+ if (status != OK) {
+ return status;
+ }
+ mGpuVendorIDs.emplace_back(gpuVendorIdUint);
+ }
+
+ return OK;
+}
+
+std::string FeatureConfig::toString() const {
+ std::string result;
+ StringAppendF(&result, "Feature: %s\n", mFeatureName.c_str());
+ StringAppendF(&result, " Status: %s\n", mEnabled ? "enabled" : "disabled");
+ for (const auto& vendorID : mGpuVendorIDs) {
+ // vkjson outputs decimal, so print both formats.
+ StringAppendF(&result, " GPU Vendor ID: 0x%04X (%d)\n", vendorID, vendorID);
+ }
+
+ return result;
+}
+
+status_t FeatureOverrides::writeToParcel(Parcel* parcel) const {
+ status_t status;
+ // Number of global feature configs.
+ status = parcel->writeVectorSize(mGlobalFeatures);
+ if (status != OK) {
+ return status;
+ }
+ // Global feature configs.
+ for (const auto& cfg : mGlobalFeatures) {
+ status = cfg.writeToParcel(parcel);
+ if (status != OK) {
+ return status;
+ }
+ }
+ // Number of package feature overrides.
+ status = parcel->writeInt32(static_cast<int32_t>(mPackageFeatures.size()));
+ if (status != OK) {
+ return status;
+ }
+ for (const auto& feature : mPackageFeatures) {
+ // Package name.
+ status = parcel->writeUtf8AsUtf16(feature.first);
+ if (status != OK) {
+ return status;
+ }
+ // Number of package feature configs.
+ status = parcel->writeVectorSize(feature.second);
+ if (status != OK) {
+ return status;
+ }
+ // Package feature configs.
+ for (const auto& cfg : feature.second) {
+ status = cfg.writeToParcel(parcel);
+ if (status != OK) {
+ return status;
+ }
+ }
+ }
+
+ return OK;
+}
+
+status_t FeatureOverrides::readFromParcel(const Parcel* parcel) {
+ status_t status;
+
+ // Number of global feature configs.
+ status = parcel->resizeOutVector(&mGlobalFeatures);
+ if (status != OK) {
+ return status;
+ }
+ // Global feature configs.
+ for (FeatureConfig& cfg : mGlobalFeatures) {
+ status = cfg.readFromParcel(parcel);
+ if (status != OK) {
+ return status;
+ }
+ }
+
+ // Number of package feature overrides.
+ int numPkgOverrides;
+ status = parcel->readInt32(&numPkgOverrides);
+ if (status != OK) {
+ return status;
+ }
+ // Package feature overrides.
+ for (int i = 0; i < numPkgOverrides; i++) {
+ // Package name.
+ std::string name;
+ status = parcel->readUtf8FromUtf16(&name);
+ if (status != OK) {
+ return status;
+ }
+ std::vector<FeatureConfig> cfgs;
+ // Number of package feature configs.
+ int numCfgs;
+ status = parcel->readInt32(&numCfgs);
+ if (status != OK) {
+ return status;
+ }
+ // Package feature configs.
+ for (int j = 0; j < numCfgs; j++) {
+ FeatureConfig cfg;
+ status = cfg.readFromParcel(parcel);
+ if (status != OK) {
+ return status;
+ }
+ cfgs.emplace_back(cfg);
+ }
+ mPackageFeatures[name] = cfgs;
+ }
+
+ return OK;
+}
+
+std::string FeatureOverrides::toString() const {
+ std::string result;
+ result.append("Global Features:\n");
+ for (auto& cfg : mGlobalFeatures) {
+ result.append(" " + cfg.toString());
+ }
+ result.append("\n");
+ result.append("Package Features:\n");
+ for (const auto& packageFeature : mPackageFeatures) {
+ result.append(" Package:");
+ StringAppendF(&result, " %s\n", packageFeature.first.c_str());
+ for (auto& cfg : packageFeature.second) {
+ result.append(" " + cfg.toString());
+ }
+ }
+
+ return result;
+}
+
+} // namespace android
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 4874dbde9c..626581cc2a 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -29,6 +29,7 @@
#include <android-base/strings.h>
#include <android/dlext.h>
#include <binder/IServiceManager.h>
+#include <com_android_graphics_graphicsenv_flags.h>
#include <graphicsenv/IGpuService.h>
#include <log/log.h>
#include <nativeloader/dlext_namespaces.h>
@@ -70,6 +71,8 @@ static bool isVndkEnabled() {
}
} // namespace
+namespace graphicsenv_flags = com::android::graphics::graphicsenv::flags;
+
namespace android {
enum NativeLibrary {
@@ -618,16 +621,62 @@ void GraphicsEnv::setAngleInfo(const std::string& path, const bool shouldUseNati
mShouldUseAngle = true;
}
mShouldUseNativeDriver = shouldUseNativeDriver;
+
+ if (mShouldUseAngle) {
+ updateAngleFeatureOverrides();
+ }
}
std::string& GraphicsEnv::getPackageName() {
return mPackageName;
}
+// List of ANGLE features to enable, specified in the Global.Settings value "angle_egl_features".
const std::vector<std::string>& GraphicsEnv::getAngleEglFeatures() {
return mAngleEglFeatures;
}
+// List of ANGLE features to override (enabled or disable).
+// The list of overrides is loaded and parsed by GpuService.
+void GraphicsEnv::updateAngleFeatureOverrides() {
+ if (!graphicsenv_flags::feature_overrides()) {
+ return;
+ }
+
+ const sp<IGpuService> gpuService = getGpuService();
+ if (!gpuService) {
+ ALOGE("No GPU service");
+ return;
+ }
+
+ mFeatureOverrides = gpuService->getFeatureOverrides();
+}
+
+void GraphicsEnv::getAngleFeatureOverrides(std::vector<const char*>& enabled,
+ std::vector<const char*>& disabled) {
+ if (!graphicsenv_flags::feature_overrides()) {
+ return;
+ }
+
+ for (const FeatureConfig& feature : mFeatureOverrides.mGlobalFeatures) {
+ if (feature.mEnabled) {
+ enabled.push_back(feature.mFeatureName.c_str());
+ } else {
+ disabled.push_back(feature.mFeatureName.c_str());
+ }
+ }
+
+ if (mFeatureOverrides.mPackageFeatures.count(mPackageName)) {
+ for (const FeatureConfig& feature : mFeatureOverrides.mPackageFeatures[mPackageName]) {
+ if (feature.mEnabled) {
+ enabled.push_back(feature.mFeatureName.c_str());
+ } else {
+ disabled.push_back(feature.mFeatureName.c_str());
+ }
+ }
+ }
+}
+
android_namespace_t* GraphicsEnv::getAngleNamespace() {
std::lock_guard<std::mutex> lock(mNamespaceMutex);
diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp
index 42e7c378a9..9a34aff299 100644
--- a/libs/graphicsenv/IGpuService.cpp
+++ b/libs/graphicsenv/IGpuService.cpp
@@ -119,6 +119,21 @@ public:
}
return driverPath;
}
+
+ FeatureOverrides getFeatureOverrides() override {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
+
+ FeatureOverrides featureOverrides;
+ status_t error =
+ remote()->transact(BnGpuService::GET_FEATURE_CONFIG_OVERRIDES, data, &reply);
+ if (error != OK) {
+ return featureOverrides;
+ }
+
+ featureOverrides.readFromParcel(&reply);
+ return featureOverrides;
+ }
};
IMPLEMENT_META_INTERFACE(GpuService, "android.graphicsenv.IGpuService");
@@ -271,6 +286,15 @@ status_t BnGpuService::onTransact(uint32_t code, const Parcel& data, Parcel* rep
toggleAngleAsSystemDriver(enableAngleAsSystemDriver);
return OK;
}
+ case GET_FEATURE_CONFIG_OVERRIDES: {
+ CHECK_INTERFACE(IGpuService, data, reply);
+
+ // Get the FeatureOverrides from gpuservice, which implements the IGpuService interface
+ // with GpuService::getFeatureOverrides().
+ FeatureOverrides featureOverrides = getFeatureOverrides();
+ featureOverrides.writeToParcel(reply);
+ return OK;
+ }
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/graphicsenv/graphicsenv_flags.aconfig b/libs/graphicsenv/graphicsenv_flags.aconfig
new file mode 100644
index 0000000000..ac66362242
--- /dev/null
+++ b/libs/graphicsenv/graphicsenv_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.graphics.graphicsenv.flags"
+container: "system"
+
+flag {
+ name: "feature_overrides"
+ namespace: "core_graphics"
+ description: "This flag controls the Feature Overrides in GraphicsEnv."
+ bug: "372694741"
+}
diff --git a/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h b/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h
new file mode 100644
index 0000000000..5dc613b901
--- /dev/null
+++ b/libs/graphicsenv/include/graphicsenv/FeatureOverrides.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 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 <map>
+#include <string>
+#include <vector>
+
+#include <binder/Parcelable.h>
+
+namespace android {
+
+class FeatureConfig : public Parcelable {
+public:
+ FeatureConfig() = default;
+ FeatureConfig(const FeatureConfig&) = default;
+ virtual ~FeatureConfig() = default;
+ virtual status_t writeToParcel(Parcel* parcel) const;
+ virtual status_t readFromParcel(const Parcel* parcel);
+ std::string toString() const;
+
+ std::string mFeatureName;
+ bool mEnabled;
+ std::vector<uint32_t> mGpuVendorIDs;
+};
+
+/*
+ * Class for transporting OpenGL ES Feature configurations from GpuService to authorized
+ * recipients.
+ */
+class FeatureOverrides : public Parcelable {
+public:
+ FeatureOverrides() = default;
+ FeatureOverrides(const FeatureOverrides&) = default;
+ virtual ~FeatureOverrides() = default;
+ virtual status_t writeToParcel(Parcel* parcel) const;
+ virtual status_t readFromParcel(const Parcel* parcel);
+ std::string toString() const;
+
+ std::vector<FeatureConfig> mGlobalFeatures;
+ /* Key: Package Name, Value: Package's Feature Configs */
+ std::map<std::string, std::vector<FeatureConfig>> mPackageFeatures;
+};
+
+} // namespace android
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index 452e48bb75..68219008e8 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_UI_GRAPHICS_ENV_H
#define ANDROID_UI_GRAPHICS_ENV_H 1
+#include <graphicsenv/FeatureOverrides.h>
#include <graphicsenv/GpuStatsInfo.h>
#include <mutex>
@@ -120,6 +121,9 @@ public:
// Get the app package name.
std::string& getPackageName();
const std::vector<std::string>& getAngleEglFeatures();
+ void updateAngleFeatureOverrides();
+ void getAngleFeatureOverrides(std::vector<const char*>& enabled,
+ std::vector<const char*>& disabled);
// Set the persist.graphics.egl system property value.
void nativeToggleAngleAsSystemDriver(bool enabled);
bool shouldUseSystemAngle();
@@ -177,6 +181,7 @@ private:
std::string mPackageName;
// ANGLE EGL features;
std::vector<std::string> mAngleEglFeatures;
+ FeatureOverrides mFeatureOverrides;
// Whether ANGLE should be used.
bool mShouldUseAngle = false;
// Whether loader should load system ANGLE.
diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h
index a0d6e37302..442683a3e9 100644
--- a/libs/graphicsenv/include/graphicsenv/IGpuService.h
+++ b/libs/graphicsenv/include/graphicsenv/IGpuService.h
@@ -18,6 +18,7 @@
#include <binder/IInterface.h>
#include <cutils/compiler.h>
+#include <graphicsenv/FeatureOverrides.h>
#include <graphicsenv/GpuStatsInfo.h>
#include <vector>
@@ -55,6 +56,9 @@ public:
// sets ANGLE as system GLES driver if enabled==true by setting persist.graphics.egl to true.
virtual void toggleAngleAsSystemDriver(bool enabled) = 0;
+
+ // Get the list of features to override.
+ virtual FeatureOverrides getFeatureOverrides() = 0;
};
class BnGpuService : public BnInterface<IGpuService> {
@@ -67,6 +71,7 @@ public:
TOGGLE_ANGLE_AS_SYSTEM_DRIVER,
SET_TARGET_STATS_ARRAY,
ADD_VULKAN_ENGINE_NAME,
+ GET_FEATURE_CONFIG_OVERRIDES,
// Always append new enum to the end.
};
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 1243b214d3..5ab31dbaba 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -264,9 +264,8 @@ filegroup {
"DisplayEventDispatcher.cpp",
"DisplayEventReceiver.cpp",
"FenceMonitor.cpp",
+ "Flags.cpp",
"GLConsumer.cpp",
- "IConsumerListener.cpp",
- "IGraphicBufferConsumer.cpp",
"IGraphicBufferProducer.cpp",
"IProducerListener.cpp",
"ISurfaceComposer.cpp",
@@ -274,6 +273,7 @@ filegroup {
"LayerMetadata.cpp",
"LayerStatePermissions.cpp",
"LayerState.cpp",
+ "DisplayLuts.cpp",
"OccupancyTracker.cpp",
"StreamSplitter.cpp",
"ScreenCaptureResults.cpp",
@@ -341,6 +341,10 @@ cc_library_shared {
"libgui_aidl_headers",
],
+ static_libs: [
+ "libsurfaceflingerflags",
+ ],
+
afdo: true,
lto: {
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index ba82046388..340b84cfcf 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -50,9 +50,27 @@ using namespace com::android::graphics::libgui;
using namespace std::chrono_literals;
namespace {
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+template <class Mutex>
+class UnlockGuard {
+public:
+ explicit UnlockGuard(Mutex& lock) : mLock{lock} { mLock.unlock(); }
+
+ ~UnlockGuard() { mLock.lock(); }
+
+ UnlockGuard(const UnlockGuard&) = delete;
+ UnlockGuard& operator=(const UnlockGuard&) = delete;
+
+private:
+ Mutex& mLock;
+};
+#endif
+
inline const char* boolToString(bool b) {
return b ? "true" : "false";
}
+
} // namespace
namespace android {
@@ -77,12 +95,6 @@ namespace android {
std::unique_lock _lock{mutex}; \
base::ScopedLockAssertion assumeLocked(mutex);
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
-static ReleaseBufferCallback EMPTY_RELEASE_CALLBACK =
- [](const ReleaseCallbackId&, const sp<Fence>& /*releaseFence*/,
- std::optional<uint32_t> /*currentMaxAcquiredBufferCount*/) {};
-#endif
-
void BLASTBufferItemConsumer::onDisconnect() {
Mutex::Autolock lock(mMutex);
mPreviouslyConnected = mCurrentlyConnected;
@@ -185,15 +197,15 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati
mUpdateDestinationFrame(updateDestinationFrame) {
createBufferQueue(&mProducer, &mConsumer);
#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
- mBufferItemConsumer = new BLASTBufferItemConsumer(mProducer, mConsumer,
- GraphicBuffer::USAGE_HW_COMPOSER |
- GraphicBuffer::USAGE_HW_TEXTURE,
- 1, false, this);
+ mBufferItemConsumer = sp<BLASTBufferItemConsumer>::make(mProducer, mConsumer,
+ GraphicBuffer::USAGE_HW_COMPOSER |
+ GraphicBuffer::USAGE_HW_TEXTURE,
+ 1, false, this);
#else
- mBufferItemConsumer = new BLASTBufferItemConsumer(mConsumer,
- GraphicBuffer::USAGE_HW_COMPOSER |
- GraphicBuffer::USAGE_HW_TEXTURE,
- 1, false, this);
+ mBufferItemConsumer = sp<BLASTBufferItemConsumer>::make(mConsumer,
+ GraphicBuffer::USAGE_HW_COMPOSER |
+ GraphicBuffer::USAGE_HW_TEXTURE,
+ 1, false, this);
#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
// since the adapter is in the client process, set dequeue timeout
// explicitly so that dequeueBuffer will block
@@ -223,20 +235,13 @@ BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinati
this);
#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
- std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> bufferReleaseConsumer;
- gui::BufferReleaseChannel::open(mName, bufferReleaseConsumer, mBufferReleaseProducer);
- mBufferReleaseReader = std::make_shared<BufferReleaseReader>(std::move(bufferReleaseConsumer));
+ gui::BufferReleaseChannel::open(mName, mBufferReleaseConsumer, mBufferReleaseProducer);
+ mBufferReleaseReader.emplace(*this);
#endif
BQA_LOGV("BLASTBufferQueue created");
}
-BLASTBufferQueue::BLASTBufferQueue(const std::string& name, const sp<SurfaceControl>& surface,
- int width, int height, int32_t format)
- : BLASTBufferQueue(name) {
- update(surface, width, height, format);
-}
-
BLASTBufferQueue::~BLASTBufferQueue() {
TransactionCompletedListener::getInstance()->removeQueueStallListener(this);
if (mPendingTransactions.empty()) {
@@ -257,9 +262,6 @@ BLASTBufferQueue::~BLASTBufferQueue() {
void BLASTBufferQueue::onFirstRef() {
// safe default, most producers are expected to override this
mProducer->setMaxDequeuedBufferCount(2);
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
- mBufferReleaseThread.start(sp<BLASTBufferQueue>::fromExisting(this));
-#endif
}
void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height,
@@ -276,18 +278,23 @@ void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width,
if (surfaceControlChanged && mSurfaceControl != nullptr) {
BQA_LOGD("Updating SurfaceControl without recreating BBQ");
}
- bool applyTransaction = false;
// Always update the native object even though they might have the same layer handle, so we can
// get the updated transform hint from WM.
mSurfaceControl = surface;
SurfaceComposerClient::Transaction t;
+ bool applyTransaction = false;
if (surfaceControlChanged) {
- t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure,
- layer_state_t::eEnableBackpressure);
#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
- t.setBufferReleaseChannel(mSurfaceControl, mBufferReleaseProducer);
+ updateBufferReleaseProducer();
#endif
+ t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure,
+ layer_state_t::eEnableBackpressure);
+ // Migrate the picture profile handle to the new surface control.
+ if (com_android_graphics_libgui_flags_apply_picture_profiles() &&
+ mPictureProfileHandle.has_value()) {
+ t.setPictureProfileHandle(mSurfaceControl, *mPictureProfileHandle);
+ }
applyTransaction = true;
}
mTransformHint = mSurfaceControl->getTransformHint();
@@ -311,7 +318,7 @@ void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width,
}
if (applyTransaction) {
// All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
- t.setApplyToken(mApplyToken).apply(false, true);
+ t.setApplyToken(mApplyToken).apply(false /* synchronous */, true /* oneWay */);
}
}
@@ -405,23 +412,19 @@ void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence
stat.latchTime,
stat.frameEventStats.dequeueReadyTime);
}
-#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
auto currFrameNumber = stat.frameEventStats.frameNumber;
- std::vector<ReleaseCallbackId> staleReleases;
- for (const auto& [key, value]: mSubmitted) {
- if (currFrameNumber > key.framenumber) {
- staleReleases.push_back(key);
+ // Release stale buffers.
+ for (const auto& [key, _] : mSubmitted) {
+ if (currFrameNumber <= key.framenumber) {
+ continue; // not stale.
}
- }
- for (const auto& staleRelease : staleReleases) {
- releaseBufferCallbackLocked(staleRelease,
+ releaseBufferCallbackLocked(key,
stat.previousReleaseFence
? stat.previousReleaseFence
: Fence::NO_FENCE,
stat.currentMaxAcquiredBufferCount,
true /* fakeRelease */);
}
-#endif
} else {
BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback");
}
@@ -455,6 +458,9 @@ ReleaseBufferCallback BLASTBufferQueue::makeReleaseBufferCallbackThunk() {
return;
}
bbq->releaseBufferCallback(id, releaseFence, currentMaxAcquiredBufferCount);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ bbq->drainBufferReleaseConsumer();
+#endif
};
}
@@ -521,8 +527,6 @@ void BLASTBufferQueue::releaseBuffer(const ReleaseCallbackId& callbackId,
const sp<Fence>& releaseFence) {
auto it = mSubmitted.find(callbackId);
if (it == mSubmitted.end()) {
- BQA_LOGE("ERROR: releaseBufferCallback without corresponding submitted buffer %s",
- callbackId.to_string().c_str());
return;
}
mNumAcquired--;
@@ -610,7 +614,7 @@ status_t BLASTBufferQueue::acquireNextBufferLocked(
mNumAcquired++;
mLastAcquiredFrameNumber = bufferItem.mFrameNumber;
ReleaseCallbackId releaseCallbackId(buffer->getId(), mLastAcquiredFrameNumber);
- mSubmitted[releaseCallbackId] = bufferItem;
+ mSubmitted.emplace_or_replace(releaseCallbackId, bufferItem);
bool needsDisconnect = false;
mBufferItemConsumer->getConnectionEvents(bufferItem.mFrameNumber, &needsDisconnect);
@@ -632,12 +636,7 @@ status_t BLASTBufferQueue::acquireNextBufferLocked(
bufferItem.mGraphicBuffer->getHeight(), bufferItem.mTransform,
bufferItem.mScalingMode, crop);
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
- ReleaseBufferCallback releaseBufferCallback =
- applyTransaction ? EMPTY_RELEASE_CALLBACK : makeReleaseBufferCallbackThunk();
-#else
auto releaseBufferCallback = makeReleaseBufferCallbackThunk();
-#endif
sp<Fence> fence = bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE;
nsecs_t dequeueTime = -1;
@@ -675,6 +674,17 @@ status_t BLASTBufferQueue::acquireNextBufferLocked(
if (!bufferItem.mIsAutoTimestamp) {
t->setDesiredPresentTime(bufferItem.mTimestamp);
}
+ if (com_android_graphics_libgui_flags_apply_picture_profiles() &&
+ bufferItem.mPictureProfileHandle.has_value()) {
+ t->setPictureProfileHandle(mSurfaceControl, *bufferItem.mPictureProfileHandle);
+ // The current picture profile must be maintained in case the BBQ gets its
+ // SurfaceControl switched out.
+ mPictureProfileHandle = bufferItem.mPictureProfileHandle;
+ // Clear out the picture profile if the requestor has asked for it to be cleared
+ if (mPictureProfileHandle == PictureProfileHandle::NONE) {
+ mPictureProfileHandle = std::nullopt;
+ }
+ }
// Drop stale frame timeline infos
while (!mPendingFrameTimelines.empty() &&
@@ -837,7 +847,7 @@ void BLASTBufferQueue::onFrameReplaced(const BufferItem& item) {
void BLASTBufferQueue::onFrameDequeued(const uint64_t bufferId) {
std::lock_guard _lock{mTimestampMutex};
- mDequeueTimestamps[bufferId] = systemTime();
+ mDequeueTimestamps.emplace_or_replace(bufferId, systemTime());
};
void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) {
@@ -1012,7 +1022,7 @@ void BLASTBufferQueue::mergeWithNextTransaction(SurfaceComposerClient::Transacti
std::lock_guard _lock{mMutex};
if (mLastAcquiredFrameNumber >= frameNumber) {
// Apply the transaction since we have already acquired the desired frame.
- t->apply();
+ t->setApplyToken(mApplyToken).apply();
} else {
mPendingTransactions.emplace_back(frameNumber, *t);
// Clear the transaction so it can't be applied elsewhere.
@@ -1110,10 +1120,10 @@ ANDROID_SINGLETON_STATIC_INSTANCE(AsyncWorker);
class AsyncProducerListener : public BnProducerListener {
private:
const sp<IProducerListener> mListener;
-
-public:
AsyncProducerListener(const sp<IProducerListener>& listener) : mListener(listener) {}
+ friend class sp<AsyncProducerListener>;
+public:
void onBufferReleased() override {
AsyncWorker::getInstance().post([listener = mListener]() { listener->onBufferReleased(); });
}
@@ -1135,6 +1145,24 @@ public:
#endif
};
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+class BBQBufferQueueCore : public BufferQueueCore {
+public:
+ explicit BBQBufferQueueCore(const wp<BLASTBufferQueue>& bbq) : mBLASTBufferQueue{bbq} {}
+
+ void notifyBufferReleased() const override {
+ sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
+ if (!bbq) {
+ return;
+ }
+ bbq->mBufferReleaseReader->interruptBlockingRead();
+ }
+
+private:
+ wp<BLASTBufferQueue> mBLASTBufferQueue;
+};
+#endif
+
// Extends the BufferQueueProducer to create a wrapper around the listener so the listener calls
// can be non-blocking when the producer is in the client process.
class BBQBufferQueueProducer : public BufferQueueProducer {
@@ -1149,7 +1177,7 @@ public:
return BufferQueueProducer::connect(listener, api, producerControlledByApp, output);
}
- return BufferQueueProducer::connect(new AsyncProducerListener(listener), api,
+ return BufferQueueProducer::connect(sp<AsyncProducerListener>::make(listener), api,
producerControlledByApp, output);
}
@@ -1186,6 +1214,48 @@ public:
return BufferQueueProducer::query(what, value);
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ status_t waitForBufferRelease(std::unique_lock<std::mutex>& bufferQueueLock,
+ nsecs_t timeout) const override {
+ const auto startTime = std::chrono::steady_clock::now();
+ sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
+ if (!bbq) {
+ return OK;
+ }
+
+ // BufferQueue has already checked if we have a free buffer. If there's an unread interrupt,
+ // we want to ignore it. This must be done before unlocking the BufferQueue lock to ensure
+ // we don't miss an interrupt.
+ bbq->mBufferReleaseReader->clearInterrupts();
+ UnlockGuard unlockGuard{bufferQueueLock};
+
+ ATRACE_FORMAT("waiting for free buffer");
+ ReleaseCallbackId id;
+ sp<Fence> fence;
+ uint32_t maxAcquiredBufferCount;
+ status_t status =
+ bbq->mBufferReleaseReader->readBlocking(id, fence, maxAcquiredBufferCount, timeout);
+ if (status == TIMED_OUT) {
+ return TIMED_OUT;
+ } else if (status != OK) {
+ // Waiting was interrupted or an error occurred. BufferQueueProducer will check if we
+ // have a free buffer and call this method again if not.
+ return OK;
+ }
+
+ bbq->releaseBufferCallback(id, fence, maxAcquiredBufferCount);
+ const nsecs_t durationNanos = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now() - startTime)
+ .count();
+ // Provide a callback for Choreographer to start buffer stuffing recovery when blocked
+ // on buffer release.
+ std::function<void(const nsecs_t)> callbackCopy = bbq->getWaitForBufferReleaseCallback();
+ if (callbackCopy) callbackCopy(durationNanos);
+
+ return OK;
+ }
+#endif
+
private:
const wp<BLASTBufferQueue> mBLASTBufferQueue;
};
@@ -1199,14 +1269,18 @@ void BLASTBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer
LOG_ALWAYS_FATAL_IF(outProducer == nullptr, "BLASTBufferQueue: outProducer must not be NULL");
LOG_ALWAYS_FATAL_IF(outConsumer == nullptr, "BLASTBufferQueue: outConsumer must not be NULL");
- sp<BufferQueueCore> core(new BufferQueueCore());
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ auto core = sp<BBQBufferQueueCore>::make(this);
+#else
+ auto core = sp<BufferQueueCore>::make();
+#endif
LOG_ALWAYS_FATAL_IF(core == nullptr, "BLASTBufferQueue: failed to create BufferQueueCore");
- sp<IGraphicBufferProducer> producer(new BBQBufferQueueProducer(core, this));
+ auto producer = sp<BBQBufferQueueProducer>::make(core, this);
LOG_ALWAYS_FATAL_IF(producer == nullptr,
"BLASTBufferQueue: failed to create BBQBufferQueueProducer");
- sp<BufferQueueConsumer> consumer(new BufferQueueConsumer(core));
+ auto consumer = sp<BufferQueueConsumer>::make(core);
consumer->setAllowExtraAcquire(true);
LOG_ALWAYS_FATAL_IF(consumer == nullptr,
"BLASTBufferQueue: failed to create BufferQueueConsumer");
@@ -1269,12 +1343,50 @@ void BLASTBufferQueue::setApplyToken(sp<IBinder> applyToken) {
mApplyToken = std::move(applyToken);
}
+void BLASTBufferQueue::setWaitForBufferReleaseCallback(
+ std::function<void(const nsecs_t)> callback) {
+ std::lock_guard _lock{mWaitForBufferReleaseMutex};
+ mWaitForBufferReleaseCallback = std::move(callback);
+}
+
+std::function<void(const nsecs_t)> BLASTBufferQueue::getWaitForBufferReleaseCallback() const {
+ std::lock_guard _lock{mWaitForBufferReleaseMutex};
+ return mWaitForBufferReleaseCallback;
+}
+
#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
-BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader(
- std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> endpoint)
- : mEndpoint{std::move(endpoint)} {
- mEpollFd = android::base::unique_fd{epoll_create1(0)};
+void BLASTBufferQueue::updateBufferReleaseProducer() {
+ // SELinux policy may prevent this process from sending the BufferReleaseChannel's file
+ // descriptor to SurfaceFlinger, causing the entire transaction to be dropped. We send this
+ // transaction independently of any other updates to ensure those updates aren't lost.
+ SurfaceComposerClient::Transaction t;
+ status_t status = t.setApplyToken(mApplyToken)
+ .setBufferReleaseChannel(mSurfaceControl, mBufferReleaseProducer)
+ .apply(false /* synchronous */, true /* oneWay */);
+ if (status != OK) {
+ ALOGW("[%s] %s - failed to set buffer release channel on %s", mName.c_str(),
+ statusToString(status).c_str(), mSurfaceControl->getName().c_str());
+ }
+}
+
+void BLASTBufferQueue::drainBufferReleaseConsumer() {
+ ATRACE_CALL();
+ while (true) {
+ ReleaseCallbackId id;
+ sp<Fence> fence;
+ uint32_t maxAcquiredBufferCount;
+ status_t status =
+ mBufferReleaseConsumer->readReleaseFence(id, fence, maxAcquiredBufferCount);
+ if (status != OK) {
+ return;
+ }
+ releaseBufferCallback(id, fence, maxAcquiredBufferCount);
+ }
+}
+
+BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader(BLASTBufferQueue& bbq) : mBbq{bbq} {
+ mEpollFd = android::base::unique_fd{epoll_create1(EPOLL_CLOEXEC)};
LOG_ALWAYS_FATAL_IF(!mEpollFd.ok(),
"Failed to create buffer release epoll file descriptor. errno=%d "
"message='%s'",
@@ -1282,9 +1394,9 @@ BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader(
epoll_event registerEndpointFd{};
registerEndpointFd.events = EPOLLIN;
- registerEndpointFd.data.fd = mEndpoint->getFd();
- status_t status =
- epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mEndpoint->getFd(), &registerEndpointFd);
+ registerEndpointFd.data.fd = mBbq.mBufferReleaseConsumer->getFd();
+ status_t status = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mBbq.mBufferReleaseConsumer->getFd(),
+ &registerEndpointFd);
LOG_ALWAYS_FATAL_IF(status == -1,
"Failed to register buffer release consumer file descriptor with epoll. "
"errno=%d message='%s'",
@@ -1306,78 +1418,64 @@ BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader(
errno, strerror(errno));
}
-BLASTBufferQueue::BufferReleaseReader& BLASTBufferQueue::BufferReleaseReader::operator=(
- BufferReleaseReader&& other) {
- if (this != &other) {
- ftl::FakeGuard guard{mMutex};
- ftl::FakeGuard otherGuard{other.mMutex};
- mEndpoint = std::move(other.mEndpoint);
- mEpollFd = std::move(other.mEpollFd);
- mEventFd = std::move(other.mEventFd);
- }
- return *this;
-}
-
status_t BLASTBufferQueue::BufferReleaseReader::readBlocking(ReleaseCallbackId& outId,
sp<Fence>& outFence,
- uint32_t& outMaxAcquiredBufferCount) {
- epoll_event event{};
- while (true) {
- int eventCount = epoll_wait(mEpollFd.get(), &event, 1 /* maxevents */, -1 /* timeout */);
- if (eventCount == 1) {
- break;
- }
- if (eventCount == -1 && errno != EINTR) {
- ALOGE("epoll_wait error while waiting for buffer release. errno=%d message='%s'", errno,
- strerror(errno));
+ uint32_t& outMaxAcquiredBufferCount,
+ nsecs_t timeout) {
+ // TODO(b/363290953) epoll_wait only has millisecond timeout precision. If timeout is less than
+ // 1ms, then we round timeout up to 1ms. Otherwise, we round timeout to the nearest
+ // millisecond. Once epoll_pwait2 can be used in libgui, we can specify timeout with nanosecond
+ // precision.
+ int timeoutMs = -1;
+ if (timeout == 0) {
+ timeoutMs = 0;
+ } else if (timeout > 0) {
+ const int nsPerMs = 1000000;
+ if (timeout < nsPerMs) {
+ timeoutMs = 1;
+ } else {
+ timeoutMs = static_cast<int>(
+ std::chrono::round<std::chrono::milliseconds>(std::chrono::nanoseconds{timeout})
+ .count());
}
}
+ epoll_event event{};
+ int eventCount;
+ do {
+ eventCount = epoll_wait(mEpollFd.get(), &event, 1 /*maxevents*/, timeoutMs);
+ } while (eventCount == -1 && errno != EINTR);
+
+ if (eventCount == -1) {
+ ALOGE("epoll_wait error while waiting for buffer release. errno=%d message='%s'", errno,
+ strerror(errno));
+ return UNKNOWN_ERROR;
+ }
+
+ if (eventCount == 0) {
+ return TIMED_OUT;
+ }
+
if (event.data.fd == mEventFd.get()) {
- uint64_t value;
- if (read(mEventFd.get(), &value, sizeof(uint64_t)) == -1 && errno != EWOULDBLOCK) {
- ALOGE("error while reading from eventfd. errno=%d message='%s'", errno,
- strerror(errno));
- }
+ clearInterrupts();
return WOULD_BLOCK;
}
- std::lock_guard lock{mMutex};
- return mEndpoint->readReleaseFence(outId, outFence, outMaxAcquiredBufferCount);
+ return mBbq.mBufferReleaseConsumer->readReleaseFence(outId, outFence,
+ outMaxAcquiredBufferCount);
}
void BLASTBufferQueue::BufferReleaseReader::interruptBlockingRead() {
- uint64_t value = 1;
- if (write(mEventFd.get(), &value, sizeof(uint64_t)) == -1) {
+ if (eventfd_write(mEventFd.get(), 1) == -1) {
ALOGE("failed to notify dequeue event. errno=%d message='%s'", errno, strerror(errno));
}
}
-void BLASTBufferQueue::BufferReleaseThread::start(const sp<BLASTBufferQueue>& bbq) {
- mRunning = std::make_shared<std::atomic_bool>(true);
- mReader = bbq->mBufferReleaseReader;
- std::thread([running = mRunning, reader = mReader, weakBbq = wp<BLASTBufferQueue>(bbq)]() {
- pthread_setname_np(pthread_self(), "BufferReleaseThread");
- while (*running) {
- ReleaseCallbackId id;
- sp<Fence> fence;
- uint32_t maxAcquiredBufferCount;
- if (status_t status = reader->readBlocking(id, fence, maxAcquiredBufferCount);
- status != OK) {
- continue;
- }
- sp<BLASTBufferQueue> bbq = weakBbq.promote();
- if (!bbq) {
- return;
- }
- bbq->releaseBufferCallback(id, fence, maxAcquiredBufferCount);
- }
- }).detach();
-}
-
-BLASTBufferQueue::BufferReleaseThread::~BufferReleaseThread() {
- *mRunning = false;
- mReader->interruptBlockingRead();
+void BLASTBufferQueue::BufferReleaseReader::clearInterrupts() {
+ eventfd_t value;
+ if (eventfd_read(mEventFd.get(), &value) == -1 && errno != EWOULDBLOCK) {
+ ALOGE("error while reading from eventfd. errno=%d message='%s'", errno, strerror(errno));
+ }
}
#endif
diff --git a/libs/gui/BufferItem.cpp b/libs/gui/BufferItem.cpp
index 5beba02e63..3b2d337a21 100644
--- a/libs/gui/BufferItem.cpp
+++ b/libs/gui/BufferItem.cpp
@@ -38,26 +38,25 @@ static inline constexpr T to64(const uint32_t lo, const uint32_t hi) {
return static_cast<T>(static_cast<uint64_t>(hi)<<32 | lo);
}
-BufferItem::BufferItem() :
- mGraphicBuffer(nullptr),
- mFence(nullptr),
- mCrop(Rect::INVALID_RECT),
- mTransform(0),
- mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
- mTimestamp(0),
- mIsAutoTimestamp(false),
- mDataSpace(HAL_DATASPACE_UNKNOWN),
- mFrameNumber(0),
- mSlot(INVALID_BUFFER_SLOT),
- mIsDroppable(false),
- mAcquireCalled(false),
- mTransformToDisplayInverse(false),
- mSurfaceDamage(),
- mAutoRefresh(false),
- mQueuedBuffer(true),
- mIsStale(false),
- mApi(0) {
-}
+BufferItem::BufferItem()
+ : mGraphicBuffer(nullptr),
+ mFence(nullptr),
+ mCrop(Rect::INVALID_RECT),
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mIsAutoTimestamp(false),
+ mDataSpace(HAL_DATASPACE_UNKNOWN),
+ mFrameNumber(0),
+ mSlot(INVALID_BUFFER_SLOT),
+ mIsDroppable(false),
+ mAcquireCalled(false),
+ mTransformToDisplayInverse(false),
+ mSurfaceDamage(),
+ mAutoRefresh(false),
+ mQueuedBuffer(true),
+ mIsStale(false),
+ mApi(0) {}
BufferItem::~BufferItem() {}
@@ -76,6 +75,11 @@ size_t BufferItem::getPodSize() const {
addAligned(size, high32(mTimestamp));
addAligned(size, mIsAutoTimestamp);
addAligned(size, mDataSpace);
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+ addAligned(size, mPictureProfileHandle.has_value());
+ addAligned(size, low32(PictureProfileHandle::NONE.getId()));
+ addAligned(size, high32(PictureProfileHandle::NONE.getId()));
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
addAligned(size, low32(mFrameNumber));
addAligned(size, high32(mFrameNumber));
addAligned(size, mSlot);
@@ -170,6 +174,16 @@ status_t BufferItem::flatten(
writeAligned(buffer, size, high32(mTimestamp));
writeAligned(buffer, size, mIsAutoTimestamp);
writeAligned(buffer, size, mDataSpace);
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+ writeAligned(buffer, size, mPictureProfileHandle.has_value());
+ if (mPictureProfileHandle.has_value()) {
+ writeAligned(buffer, size, low32(mPictureProfileHandle->getId()));
+ writeAligned(buffer, size, high32(mPictureProfileHandle->getId()));
+ } else {
+ writeAligned(buffer, size, low32(PictureProfileHandle::NONE.getId()));
+ writeAligned(buffer, size, high32(PictureProfileHandle::NONE.getId()));
+ }
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
writeAligned(buffer, size, low32(mFrameNumber));
writeAligned(buffer, size, high32(mFrameNumber));
writeAligned(buffer, size, mSlot);
@@ -231,6 +245,7 @@ status_t BufferItem::unflatten(
uint32_t timestampLo = 0, timestampHi = 0;
uint32_t frameNumberLo = 0, frameNumberHi = 0;
+ int32_t pictureProfileIdLo = 0, pictureProfileIdHi = 0;
readAligned(buffer, size, mCrop);
readAligned(buffer, size, mTransform);
@@ -240,6 +255,16 @@ status_t BufferItem::unflatten(
mTimestamp = to64<int64_t>(timestampLo, timestampHi);
readAligned(buffer, size, mIsAutoTimestamp);
readAligned(buffer, size, mDataSpace);
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+ bool hasPictureProfileHandle;
+ readAligned(buffer, size, hasPictureProfileHandle);
+ readAligned(buffer, size, pictureProfileIdLo);
+ readAligned(buffer, size, pictureProfileIdHi);
+ mPictureProfileHandle = hasPictureProfileHandle
+ ? std::optional(PictureProfileHandle(
+ to64<PictureProfileId>(pictureProfileIdLo, pictureProfileIdHi)))
+ : std::nullopt;
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
readAligned(buffer, size, frameNumberLo);
readAligned(buffer, size, frameNumberHi);
mFrameNumber = to64<uint64_t>(frameNumberLo, frameNumberHi);
diff --git a/libs/gui/BufferItemConsumer.cpp b/libs/gui/BufferItemConsumer.cpp
index bfe3d6e023..1585aae45c 100644
--- a/libs/gui/BufferItemConsumer.cpp
+++ b/libs/gui/BufferItemConsumer.cpp
@@ -24,6 +24,7 @@
#include <com_android_graphics_libgui_flags.h>
#include <gui/BufferItem.h>
#include <gui/BufferItemConsumer.h>
+#include <gui/Surface.h>
#include <ui/BufferQueueDefs.h>
#include <ui/GraphicBuffer.h>
@@ -35,6 +36,30 @@
namespace android {
+std::tuple<sp<BufferItemConsumer>, sp<Surface>> BufferItemConsumer::create(
+ uint64_t consumerUsage, int bufferCount, bool controlledByApp,
+ bool isConsumerSurfaceFlinger) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ sp<BufferItemConsumer> bufferItemConsumer =
+ sp<BufferItemConsumer>::make(consumerUsage, bufferCount, controlledByApp,
+ isConsumerSurfaceFlinger);
+ return {bufferItemConsumer, bufferItemConsumer->getSurface()};
+#else
+ sp<IGraphicBufferProducer> igbp;
+ sp<IGraphicBufferConsumer> igbc;
+ BufferQueue::createBufferQueue(&igbp, &igbc, isConsumerSurfaceFlinger);
+ sp<BufferItemConsumer> bufferItemConsumer =
+ sp<BufferItemConsumer>::make(igbc, consumerUsage, bufferCount, controlledByApp);
+ return {bufferItemConsumer, sp<Surface>::make(igbp, controlledByApp)};
+#endif
+}
+
+sp<BufferItemConsumer> BufferItemConsumer::create(const sp<IGraphicBufferConsumer>& consumer,
+ uint64_t consumerUsage, int bufferCount,
+ bool controlledByApp) {
+ return sp<BufferItemConsumer>::make(consumer, consumerUsage, bufferCount, controlledByApp);
+}
+
#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
BufferItemConsumer::BufferItemConsumer(uint64_t consumerUsage, int bufferCount,
bool controlledByApp, bool isConsumerSurfaceFlinger)
@@ -140,7 +165,7 @@ status_t BufferItemConsumer::releaseBufferSlotLocked(int slotIndex, const sp<Gra
BI_LOGE("Failed to addReleaseFenceLocked");
}
- err = releaseBufferLocked(slotIndex, buffer, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
+ err = releaseBufferLocked(slotIndex, buffer);
if (err != OK && err != IGraphicBufferConsumer::STALE_BUFFER_SLOT) {
BI_LOGE("Failed to release buffer: %s (%d)",
strerror(-err), err);
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index b0f6e69115..f1374e23fd 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -108,6 +108,15 @@ void BufferQueue::ProxyConsumerListener::onSetFrameRate(float frameRate, int8_t
}
#endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+void BufferQueue::ProxyConsumerListener::onSlotCountChanged(int slotCount) {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != nullptr) {
+ listener->onSlotCountChanged(slotCount);
+ }
+}
+#endif
+
void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
sp<IGraphicBufferConsumer>* outConsumer,
bool consumerIsSurfaceFlinger) {
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index 69d25be006..270bfbdc64 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -28,6 +28,10 @@
#define VALIDATE_CONSISTENCY()
#endif
+#define EGL_EGLEXT_PROTOTYPES
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
#include <gui/BufferItem.h>
#include <gui/BufferQueueConsumer.h>
#include <gui/BufferQueueCore.h>
@@ -297,7 +301,11 @@ status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
// We might have freed a slot while dropping old buffers, or the producer
// may be blocked waiting for the number of buffers in the queue to
// decrease.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ mCore->notifyBufferReleased();
+#else
mCore->mDequeueCondition.notify_all();
+#endif
ATRACE_INT(mCore->mConsumerName.c_str(), static_cast<int32_t>(mCore->mQueue.size()));
#ifndef NO_BINDER
@@ -333,9 +341,9 @@ status_t BufferQueueConsumer::detachBuffer(int slot) {
return BAD_VALUE;
}
- if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
- BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
- slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ const int totalSlotCount = mCore->getTotalSlotCountLocked();
+ if (slot < 0 || slot >= totalSlotCount) {
+ BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
return BAD_VALUE;
} else if (!mSlots[slot].mBufferState.isAcquired()) {
BQ_LOGE("detachBuffer: slot %d is not owned by the consumer "
@@ -350,7 +358,12 @@ status_t BufferQueueConsumer::detachBuffer(int slot) {
mCore->mActiveBuffers.erase(slot);
mCore->mFreeSlots.insert(slot);
mCore->clearBufferSlotLocked(slot);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ mCore->notifyBufferReleased();
+#else
mCore->mDequeueCondition.notify_all();
+#endif
+
VALIDATE_CONSISTENCY();
}
@@ -464,16 +477,24 @@ status_t BufferQueueConsumer::attachBuffer(int* outSlot,
return NO_ERROR;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
+ const sp<Fence>& releaseFence) {
+#else
status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
const sp<Fence>& releaseFence, EGLDisplay eglDisplay,
EGLSyncKHR eglFence) {
+#endif
ATRACE_CALL();
ATRACE_BUFFER_INDEX(slot);
- if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS ||
- releaseFence == nullptr) {
- BQ_LOGE("releaseBuffer: slot %d out of range or fence %p NULL", slot,
- releaseFence.get());
+ const int totalSlotCount = mCore->getTotalSlotCountLocked();
+ if (slot < 0 || slot >= totalSlotCount) {
+ BQ_LOGE("releaseBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
+ return BAD_VALUE;
+ }
+ if (releaseFence == nullptr) {
+ BQ_LOGE("releaseBuffer: slot %d fence %p NULL", slot, releaseFence.get());
return BAD_VALUE;
}
@@ -481,6 +502,13 @@ status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
{ // Autolock scope
std::lock_guard<std::mutex> lock(mCore->mMutex);
+ const int totalSlotCount = mCore->getTotalSlotCountLocked();
+ if (slot < 0 || slot >= totalSlotCount || releaseFence == nullptr) {
+ BQ_LOGE("releaseBuffer: slot %d out of range [0, %d) or fence %p NULL", slot,
+ totalSlotCount, releaseFence.get());
+ return BAD_VALUE;
+ }
+
// If the frame number has changed because the buffer has been reallocated,
// we can ignore this releaseBuffer for the old buffer.
// Ignore this for the shared buffer where the frame number can easily
@@ -498,8 +526,10 @@ status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
return BAD_VALUE;
}
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
mSlots[slot].mEglDisplay = eglDisplay;
mSlots[slot].mEglFence = eglFence;
+#endif
mSlots[slot].mFence = releaseFence;
mSlots[slot].mBufferState.release();
@@ -520,7 +550,12 @@ status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
}
BQ_LOGV("releaseBuffer: releasing slot %d", slot);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ mCore->notifyBufferReleased();
+#else
mCore->mDequeueCondition.notify_all();
+#endif
+
VALIDATE_CONSISTENCY();
} // Autolock scope
@@ -574,7 +609,11 @@ status_t BufferQueueConsumer::disconnect() {
mCore->mQueue.clear();
mCore->freeAllBuffersLocked();
mCore->mSharedBufferSlot = BufferQueueCore::INVALID_BUFFER_SLOT;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ mCore->notifyBufferReleased();
+#else
mCore->mDequeueCondition.notify_all();
+#endif
return NO_ERROR;
}
@@ -616,6 +655,43 @@ status_t BufferQueueConsumer::getReleasedBuffers(uint64_t *outSlotMask) {
return NO_ERROR;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueConsumer::getReleasedBuffersExtended(std::vector<bool>* outSlotMask) {
+ ATRACE_CALL();
+
+ if (outSlotMask == nullptr) {
+ BQ_LOGE("getReleasedBuffersExtended: outSlotMask may not be NULL");
+ return BAD_VALUE;
+ }
+
+ std::lock_guard<std::mutex> lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("getReleasedBuffersExtended: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ const int totalSlotCount = mCore->getTotalSlotCountLocked();
+ outSlotMask->resize(totalSlotCount);
+ for (int s = 0; s < totalSlotCount; ++s) {
+ (*outSlotMask)[s] = !mSlots[s].mAcquireCalled;
+ }
+
+ // Remove from the mask queued buffers for which acquire has been called,
+ // since the consumer will not receive their buffer addresses and so must
+ // retain their cached information
+ BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
+ while (current != mCore->mQueue.end()) {
+ if (current->mAcquireCalled) {
+ (*outSlotMask)[current->mSlot] = false;
+ }
+ ++current;
+ }
+
+ return NO_ERROR;
+}
+#endif
+
status_t BufferQueueConsumer::setDefaultBufferSize(uint32_t width,
uint32_t height) {
ATRACE_CALL();
@@ -634,6 +710,28 @@ status_t BufferQueueConsumer::setDefaultBufferSize(uint32_t width,
return NO_ERROR;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueConsumer::allowUnlimitedSlots(bool allowUnlimitedSlots) {
+ ATRACE_CALL();
+ BQ_LOGV("allowUnlimitedSlots: %d", allowUnlimitedSlots);
+ std::lock_guard<std::mutex> lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("allowUnlimitedSlots: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (mCore->mConnectedApi != BufferQueueCore::NO_CONNECTED_API) {
+ BQ_LOGE("allowUnlimitedSlots: BufferQueue already connected");
+ return INVALID_OPERATION;
+ }
+
+ mCore->mAllowExtendedSlotCount = allowUnlimitedSlots;
+
+ return OK;
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+
status_t BufferQueueConsumer::setMaxBufferCount(int bufferCount) {
ATRACE_CALL();
@@ -673,16 +771,23 @@ status_t BufferQueueConsumer::setMaxAcquiredBufferCount(
int maxAcquiredBuffers) {
ATRACE_FORMAT("%s(%d)", __func__, maxAcquiredBuffers);
- if (maxAcquiredBuffers < 1 ||
- maxAcquiredBuffers > BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) {
- BQ_LOGE("setMaxAcquiredBufferCount: invalid count %d",
- maxAcquiredBuffers);
- return BAD_VALUE;
- }
-
sp<IConsumerListener> listener;
{ // Autolock scope
std::unique_lock<std::mutex> lock(mCore->mMutex);
+
+ // We reserve two slots in order to guarantee that the producer and
+ // consumer can run asynchronously.
+ int maxMaxAcquiredBuffers =
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mCore->getTotalSlotCountLocked() - 2;
+#else
+ BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS;
+#endif
+ if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > maxMaxAcquiredBuffers) {
+ BQ_LOGE("setMaxAcquiredBufferCount: invalid count %d", maxAcquiredBuffers);
+ return BAD_VALUE;
+ }
+
mCore->waitWhileAllocatingLocked(lock);
if (mCore->mIsAbandoned) {
diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp
index e0c5b1f7d1..6c79904475 100644
--- a/libs/gui/BufferQueueCore.cpp
+++ b/libs/gui/BufferQueueCore.cpp
@@ -38,6 +38,8 @@
#include <system/window.h>
+#include <ui/BufferQueueDefs.h>
+
namespace android {
// Macros for include BufferQueueCore information in log messages
@@ -97,7 +99,11 @@ BufferQueueCore::BufferQueueCore()
mConnectedProducerListener(),
mBufferReleasedCbEnabled(false),
mBufferAttachedCbEnabled(false),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#else
mSlots(),
+#endif
mQueue(),
mFreeSlots(),
mFreeBuffers(),
@@ -111,6 +117,9 @@ BufferQueueCore::BufferQueueCore()
mDefaultWidth(1),
mDefaultHeight(1),
mDefaultBufferDataSpace(HAL_DATASPACE_UNKNOWN),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mAllowExtendedSlotCount(false),
+#endif
mMaxBufferCount(BufferQueueDefs::NUM_BUFFER_SLOTS),
mMaxAcquiredBufferCount(1),
mMaxDequeuedBufferCount(1),
@@ -221,6 +230,14 @@ void BufferQueueCore::dumpState(const String8& prefix, String8* outResult) const
}
}
+int BufferQueueCore::getTotalSlotCountLocked() const {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ return mAllowExtendedSlotCount ? mMaxBufferCount : BufferQueueDefs::NUM_BUFFER_SLOTS;
+#else
+ return BufferQueueDefs::NUM_BUFFER_SLOTS;
+#endif
+}
+
int BufferQueueCore::getMinUndequeuedBufferCountLocked() const {
// If dequeueBuffer is allowed to error out, we don't have to add an
// extra buffer.
@@ -253,6 +270,26 @@ int BufferQueueCore::getMaxBufferCountLocked() const {
return maxBufferCount;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueCore::extendSlotCountLocked(int size) {
+ int previousSize = (int)mSlots.size();
+ if (previousSize > size) {
+ return BAD_VALUE;
+ }
+ if (previousSize == size) {
+ return NO_ERROR;
+ }
+
+ mSlots.resize(size);
+ for (int i = previousSize; i < size; i++) {
+ mUnusedSlots.push_back(i);
+ }
+
+ mMaxBufferCount = size;
+ return NO_ERROR;
+}
+#endif
+
void BufferQueueCore::clearBufferSlotLocked(int slot) {
BQ_LOGV("clearBufferSlotLocked: slot %d", slot);
@@ -262,14 +299,16 @@ void BufferQueueCore::clearBufferSlotLocked(int slot) {
mSlots[slot].mFrameNumber = 0;
mSlots[slot].mAcquireCalled = false;
mSlots[slot].mNeedsReallocation = true;
+ mSlots[slot].mFence = Fence::NO_FENCE;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
// Destroy fence as BufferQueue now takes ownership
if (mSlots[slot].mEglFence != EGL_NO_SYNC_KHR) {
eglDestroySyncKHR(mSlots[slot].mEglDisplay, mSlots[slot].mEglFence);
mSlots[slot].mEglFence = EGL_NO_SYNC_KHR;
}
- mSlots[slot].mFence = Fence::NO_FENCE;
mSlots[slot].mEglDisplay = EGL_NO_DISPLAY;
+#endif
if (mLastQueuedSlot == slot) {
mLastQueuedSlot = INVALID_BUFFER_SLOT;
@@ -371,11 +410,17 @@ void BufferQueueCore::waitWhileAllocatingLocked(std::unique_lock<std::mutex>& lo
}
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+void BufferQueueCore::notifyBufferReleased() const {
+ mDequeueCondition.notify_all();
+}
+#endif
+
#if DEBUG_ONLY_CODE
void BufferQueueCore::validateConsistencyLocked() const {
static const useconds_t PAUSE_TIME = 0;
int allocatedSlots = 0;
- for (int slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
+ for (int slot = 0; slot < getTotalSlotCountLocked(); ++slot) {
bool isInFreeSlots = mFreeSlots.count(slot) != 0;
bool isInFreeBuffers =
std::find(mFreeBuffers.cbegin(), mFreeBuffers.cend(), slot) !=
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 831b2ee4be..c241482827 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -40,6 +40,7 @@
#include <gui/TraceUtils.h>
#include <private/gui/BufferQueueThreadState.h>
+#include <utils/Errors.h>
#include <utils/Log.h>
#include <utils/Trace.h>
@@ -108,9 +109,9 @@ status_t BufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
return NO_INIT;
}
- if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
- BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)",
- slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ int maxSlot = mCore->getTotalSlotCountLocked();
+ if (slot < 0 || slot >= maxSlot) {
+ BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)", slot, maxSlot);
return BAD_VALUE;
} else if (!mSlots[slot].mBufferState.isDequeued()) {
BQ_LOGE("requestBuffer: slot %d is not owned by the producer "
@@ -123,6 +124,49 @@ status_t BufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
return NO_ERROR;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t BufferQueueProducer::extendSlotCount(int size) {
+ ATRACE_CALL();
+
+ sp<IConsumerListener> listener;
+ {
+ std::lock_guard<std::mutex> lock(mCore->mMutex);
+ BQ_LOGV("extendSlotCount: size %d", size);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("extendSlotCount: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (!mCore->mAllowExtendedSlotCount) {
+ BQ_LOGE("extendSlotCount: Consumer did not allow unlimited slots");
+ return INVALID_OPERATION;
+ }
+
+ int maxBeforeExtension = mCore->mMaxBufferCount;
+
+ if (size == maxBeforeExtension) {
+ return NO_ERROR;
+ }
+
+ if (size < maxBeforeExtension) {
+ return BAD_VALUE;
+ }
+
+ if (status_t ret = mCore->extendSlotCountLocked(size); ret != OK) {
+ return ret;
+ }
+ listener = mCore->mConsumerListener;
+ }
+
+ if (listener) {
+ listener->onSlotCountChanged(size);
+ }
+
+ return NO_ERROR;
+}
+#endif
+
status_t BufferQueueProducer::setMaxDequeuedBufferCount(
int maxDequeuedBuffers) {
int maxBufferCount;
@@ -170,9 +214,10 @@ status_t BufferQueueProducer::setMaxDequeuedBufferCount(int maxDequeuedBuffers,
int bufferCount = mCore->getMinUndequeuedBufferCountLocked();
bufferCount += maxDequeuedBuffers;
- if (bufferCount > BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ if (bufferCount > mCore->getTotalSlotCountLocked()) {
BQ_LOGE("setMaxDequeuedBufferCount: bufferCount %d too large "
- "(max %d)", bufferCount, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ "(max %d)",
+ bufferCount, mCore->getTotalSlotCountLocked());
return BAD_VALUE;
}
@@ -202,7 +247,11 @@ status_t BufferQueueProducer::setMaxDequeuedBufferCount(int maxDequeuedBuffers,
if (delta < 0) {
listener = mCore->mConsumerListener;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ mCore->notifyBufferReleased();
+#else
mCore->mDequeueCondition.notify_all();
+#endif
} // Autolock scope
// Call back without lock held
@@ -254,7 +303,12 @@ status_t BufferQueueProducer::setAsyncMode(bool async) {
}
mCore->mAsyncMode = async;
VALIDATE_CONSISTENCY();
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ mCore->notifyBufferReleased();
+#else
mCore->mDequeueCondition.notify_all();
+#endif
+
if (delta < 0) {
listener = mCore->mConsumerListener;
}
@@ -376,6 +430,12 @@ status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller,
(acquiredCount <= mCore->mMaxAcquiredBufferCount)) {
return WOULD_BLOCK;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ if (status_t status = waitForBufferRelease(lock, mDequeueTimeout);
+ status == TIMED_OUT) {
+ return TIMED_OUT;
+ }
+#else
if (mDequeueTimeout >= 0) {
std::cv_status result = mCore->mDequeueCondition.wait_for(lock,
std::chrono::nanoseconds(mDequeueTimeout));
@@ -385,12 +445,29 @@ status_t BufferQueueProducer::waitForFreeSlotThenRelock(FreeSlotCaller caller,
} else {
mCore->mDequeueCondition.wait(lock);
}
+#endif
}
} // while (tryAgain)
return NO_ERROR;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+status_t BufferQueueProducer::waitForBufferRelease(std::unique_lock<std::mutex>& lock,
+ nsecs_t timeout) const {
+ if (mDequeueTimeout >= 0) {
+ std::cv_status result =
+ mCore->mDequeueCondition.wait_for(lock, std::chrono::nanoseconds(timeout));
+ if (result == std::cv_status::timeout) {
+ return TIMED_OUT;
+ }
+ } else {
+ mCore->mDequeueCondition.wait(lock);
+ }
+ return OK;
+}
+#endif
+
status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,
uint32_t width, uint32_t height, PixelFormat format,
uint64_t usage, uint64_t* outBufferAge,
@@ -419,8 +496,10 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou
}
status_t returnFlags = NO_ERROR;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
EGLDisplay eglDisplay = EGL_NO_DISPLAY;
EGLSyncKHR eglFence = EGL_NO_SYNC_KHR;
+#endif
bool attachedByConsumer = false;
sp<IConsumerListener> listener;
@@ -537,8 +616,10 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou
mSlots[found].mAcquireCalled = false;
mSlots[found].mGraphicBuffer = nullptr;
mSlots[found].mRequestBufferCalled = false;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
mSlots[found].mEglDisplay = EGL_NO_DISPLAY;
mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
+#endif
mSlots[found].mFence = Fence::NO_FENCE;
mCore->mBufferAge = 0;
mCore->mIsAllocating = true;
@@ -563,14 +644,18 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou
found, buffer->width, buffer->height, buffer->format);
}
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
eglDisplay = mSlots[found].mEglDisplay;
eglFence = mSlots[found].mEglFence;
+#endif
// Don't return a fence in shared buffer mode, except for the first
// frame.
*outFence = (mCore->mSharedBufferMode &&
mCore->mSharedBufferSlot == found) ?
Fence::NO_FENCE : mSlots[found].mFence;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
+#endif
mSlots[found].mFence = Fence::NO_FENCE;
// If shared buffer mode has just been enabled, cache the slot of the
@@ -659,6 +744,7 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou
returnFlags |= BUFFER_NEEDS_REALLOCATION;
}
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
if (eglFence != EGL_NO_SYNC_KHR) {
EGLint result = eglClientWaitSyncKHR(eglDisplay, eglFence, 0,
1000000000);
@@ -673,6 +759,7 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou
}
eglDestroySyncKHR(eglDisplay, eglFence);
}
+#endif
BQ_LOGV("dequeueBuffer: returning slot=%d/%" PRIu64 " buf=%p flags=%#x",
*outSlot,
@@ -714,9 +801,9 @@ status_t BufferQueueProducer::detachBuffer(int slot) {
return BAD_VALUE;
}
- if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
- BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
- slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ const int totalSlotCount = mCore->getTotalSlotCountLocked();
+ if (slot < 0 || slot >= totalSlotCount) {
+ BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
return BAD_VALUE;
} else if (!mSlots[slot].mBufferState.isDequeued()) {
// TODO(http://b/140581935): This message is BQ_LOGW because it
@@ -741,7 +828,11 @@ status_t BufferQueueProducer::detachBuffer(int slot) {
mCore->mActiveBuffers.erase(slot);
mCore->mFreeSlots.insert(slot);
mCore->clearBufferSlotLocked(slot);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ mCore->notifyBufferReleased();
+#else
mCore->mDequeueCondition.notify_all();
+#endif
VALIDATE_CONSISTENCY();
}
@@ -872,7 +963,9 @@ status_t BufferQueueProducer::attachBuffer(int* outSlot,
mSlots[*outSlot].mGraphicBuffer = buffer;
mSlots[*outSlot].mBufferState.attachProducer();
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
mSlots[*outSlot].mEglFence = EGL_NO_SYNC_KHR;
+#endif
mSlots[*outSlot].mFence = Fence::NO_FENCE;
mSlots[*outSlot].mRequestBufferCalled = true;
mSlots[*outSlot].mAcquireCalled = false;
@@ -902,6 +995,8 @@ status_t BufferQueueProducer::queueBuffer(int slot,
&getFrameTimestamps);
const Region& surfaceDamage = input.getSurfaceDamage();
const HdrMetadata& hdrMetadata = input.getHdrMetadata();
+ const std::optional<PictureProfileHandle>& pictureProfileHandle =
+ input.getPictureProfileHandle();
if (acquireFence == nullptr) {
BQ_LOGE("queueBuffer: fence is NULL");
@@ -943,9 +1038,9 @@ status_t BufferQueueProducer::queueBuffer(int slot,
return NO_INIT;
}
- if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
- BQ_LOGE("queueBuffer: slot index %d out of range [0, %d)",
- slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ const int totalSlotCount = mCore->getTotalSlotCountLocked();
+ if (slot < 0 || slot >= totalSlotCount) {
+ BQ_LOGE("queueBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
return BAD_VALUE;
} else if (!mSlots[slot].mBufferState.isDequeued()) {
BQ_LOGE("queueBuffer: slot %d is not owned by the producer "
@@ -1008,6 +1103,7 @@ status_t BufferQueueProducer::queueBuffer(int slot,
item.mIsAutoTimestamp = isAutoTimestamp;
item.mDataSpace = dataSpace;
item.mHdrMetadata = hdrMetadata;
+ item.mPictureProfileHandle = pictureProfileHandle;
item.mFrameNumber = currentFrameNumber;
item.mSlot = slot;
item.mFence = acquireFence;
@@ -1082,7 +1178,11 @@ status_t BufferQueueProducer::queueBuffer(int slot,
}
mCore->mBufferHasBeenQueued = true;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ mCore->notifyBufferReleased();
+#else
mCore->mDequeueCondition.notify_all();
+#endif
mCore->mLastQueuedSlot = slot;
output->width = mCore->mDefaultWidth;
@@ -1184,9 +1284,9 @@ status_t BufferQueueProducer::cancelBuffer(int slot, const sp<Fence>& fence) {
return BAD_VALUE;
}
- if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
- BQ_LOGE("cancelBuffer: slot index %d out of range [0, %d)", slot,
- BufferQueueDefs::NUM_BUFFER_SLOTS);
+ const int totalSlotCount = mCore->getTotalSlotCountLocked();
+ if (slot < 0 || slot >= totalSlotCount) {
+ BQ_LOGE("cancelBuffer: slot index %d out of range [0, %d)", slot, totalSlotCount);
return BAD_VALUE;
} else if (!mSlots[slot].mBufferState.isDequeued()) {
BQ_LOGE("cancelBuffer: slot %d is not owned by the producer "
@@ -1218,7 +1318,11 @@ status_t BufferQueueProducer::cancelBuffer(int slot, const sp<Fence>& fence) {
bufferId = gb->getId();
}
mSlots[slot].mFence = fence;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ mCore->notifyBufferReleased();
+#else
mCore->mDequeueCondition.notify_all();
+#endif
listener = mCore->mConsumerListener;
VALIDATE_CONSISTENCY();
}
@@ -1350,6 +1454,9 @@ status_t BufferQueueProducer::connect(const sp<IProducerListener>& listener,
output->nextFrameNumber = mCore->mFrameCounter + 1;
output->bufferReplaced = false;
output->maxBufferCount = mCore->mMaxBufferCount;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ output->isSlotExpansionAllowed = mCore->mAllowExtendedSlotCount;
+#endif
if (listener != nullptr) {
// Set up a death notification so that we can disconnect
@@ -1457,7 +1564,11 @@ status_t BufferQueueProducer::disconnect(int api, DisconnectMode mode) {
mCore->mConnectedApi = BufferQueueCore::NO_CONNECTED_API;
mCore->mConnectedPid = -1;
mCore->mSidebandStream.clear();
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ mCore->notifyBufferReleased();
+#else
mCore->mDequeueCondition.notify_all();
+#endif
mCore->mAutoPrerotation = false;
#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
mCore->mAdditionalOptions.clear();
diff --git a/libs/gui/BufferReleaseChannel.cpp b/libs/gui/BufferReleaseChannel.cpp
index 27367aa83f..e9cb013baf 100644
--- a/libs/gui/BufferReleaseChannel.cpp
+++ b/libs/gui/BufferReleaseChannel.cpp
@@ -35,35 +35,35 @@ namespace android::gui {
namespace {
template <typename T>
-static void readAligned(const void*& buffer, size_t& size, T& value) {
+void readAligned(const void*& buffer, size_t& size, T& value) {
size -= FlattenableUtils::align<alignof(T)>(buffer);
FlattenableUtils::read(buffer, size, value);
}
template <typename T>
-static void writeAligned(void*& buffer, size_t& size, T value) {
+void writeAligned(void*& buffer, size_t& size, T value) {
size -= FlattenableUtils::align<alignof(T)>(buffer);
FlattenableUtils::write(buffer, size, value);
}
template <typename T>
-static void addAligned(size_t& size, T /* value */) {
+void addAligned(size_t& size, T /* value */) {
size = FlattenableUtils::align<sizeof(T)>(size);
size += sizeof(T);
}
template <typename T>
-static inline constexpr uint32_t low32(const T n) {
+inline constexpr uint32_t low32(const T n) {
return static_cast<uint32_t>(static_cast<uint64_t>(n));
}
template <typename T>
-static inline constexpr uint32_t high32(const T n) {
+inline constexpr uint32_t high32(const T n) {
return static_cast<uint32_t>(static_cast<uint64_t>(n) >> 32);
}
template <typename T>
-static inline constexpr T to64(const uint32_t lo, const uint32_t hi) {
+inline constexpr T to64(const uint32_t lo, const uint32_t hi) {
return static_cast<T>(static_cast<uint64_t>(hi) << 32 | lo);
}
@@ -136,23 +136,23 @@ status_t BufferReleaseChannel::Message::unflatten(void const*& buffer, size_t& s
status_t BufferReleaseChannel::ConsumerEndpoint::readReleaseFence(
ReleaseCallbackId& outReleaseCallbackId, sp<Fence>& outReleaseFence,
uint32_t& outMaxAcquiredBufferCount) {
+ std::lock_guard lock{mMutex};
Message message;
mFlattenedBuffer.resize(message.getFlattenedSize());
- std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer;
+ std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer{};
iovec iov{
.iov_base = mFlattenedBuffer.data(),
.iov_len = mFlattenedBuffer.size(),
};
- msghdr msg{
- .msg_iov = &iov,
- .msg_iovlen = 1,
- .msg_control = controlMessageBuffer.data(),
- .msg_controllen = controlMessageBuffer.size(),
- };
+ msghdr msg{};
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = controlMessageBuffer.data();
+ msg.msg_controllen = controlMessageBuffer.size();
- int result;
+ ssize_t result;
do {
result = recvmsg(mFd, &msg, 0);
} while (result == -1 && errno == EINTR);
@@ -160,7 +160,7 @@ status_t BufferReleaseChannel::ConsumerEndpoint::readReleaseFence(
if (errno == EWOULDBLOCK || errno == EAGAIN) {
return WOULD_BLOCK;
}
- ALOGE("Error reading release fence from socket: error %#x (%s)", errno, strerror(errno));
+ ALOGE("Error reading release fence from socket: error %d (%s)", errno, strerror(errno));
return UNKNOWN_ERROR;
}
@@ -199,9 +199,9 @@ status_t BufferReleaseChannel::ConsumerEndpoint::readReleaseFence(
return OK;
}
-int BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(const ReleaseCallbackId& callbackId,
- const sp<Fence>& fence,
- uint32_t maxAcquiredBufferCount) {
+status_t BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(
+ const ReleaseCallbackId& callbackId, const sp<Fence>& fence,
+ uint32_t maxAcquiredBufferCount) {
Message message{callbackId, fence ? fence : Fence::NO_FENCE, maxAcquiredBufferCount};
mFlattenedBuffer.resize(message.getFlattenedSize());
int flattenedFd;
@@ -212,25 +212,22 @@ int BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(const ReleaseCallb
size_t flattenedBufferSize = mFlattenedBuffer.size();
int* flattenedFdPtr = &flattenedFd;
size_t flattenedFdCount = 1;
- if (status_t err = message.flatten(flattenedBufferPtr, flattenedBufferSize, flattenedFdPtr,
- flattenedFdCount);
- err != OK) {
- ALOGE("Failed to flatten BufferReleaseChannel message.");
- return err;
+ if (status_t status = message.flatten(flattenedBufferPtr, flattenedBufferSize,
+ flattenedFdPtr, flattenedFdCount);
+ status != OK) {
+ return status;
}
}
- iovec iov{
- .iov_base = mFlattenedBuffer.data(),
- .iov_len = mFlattenedBuffer.size(),
- };
+ iovec iov{};
+ iov.iov_base = mFlattenedBuffer.data();
+ iov.iov_len = mFlattenedBuffer.size();
- msghdr msg{
- .msg_iov = &iov,
- .msg_iovlen = 1,
- };
+ msghdr msg{};
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
- std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer;
+ std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer{};
if (fence && fence->isValid()) {
msg.msg_control = controlMessageBuffer.data();
msg.msg_controllen = controlMessageBuffer.size();
@@ -242,12 +239,11 @@ int BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(const ReleaseCallb
memcpy(CMSG_DATA(cmsg), &flattenedFd, sizeof(int));
}
- int result;
+ ssize_t result;
do {
result = sendmsg(mFd, &msg, 0);
} while (result == -1 && errno == EINTR);
if (result == -1) {
- ALOGD("Error writing release fence to socket: error %#x (%s)", errno, strerror(errno));
return -errno;
}
@@ -343,13 +339,6 @@ status_t BufferReleaseChannel::open(std::string name,
return -errno;
}
- // Make the producer write-only
- if (shutdown(producerFd.get(), SHUT_RD) == -1) {
- ALOGE("[%s] Failed to shutdown reading on producer socket. errno=%d message='%s'",
- name.c_str(), errno, strerror(errno));
- return -errno;
- }
-
outConsumer = std::make_unique<ConsumerEndpoint>(name, std::move(consumerFd));
outProducer = std::make_shared<ProducerEndpoint>(std::move(name), std::move(producerFd));
return STATUS_OK;
diff --git a/libs/gui/BufferStuffing.md b/libs/gui/BufferStuffing.md
new file mode 100644
index 0000000000..6ed8ad988b
--- /dev/null
+++ b/libs/gui/BufferStuffing.md
@@ -0,0 +1,7 @@
+### Buffer Stuffing and Recovery ###
+
+Buffer stuffing happens on the client side when SurfaceFlinger misses a frame, but the client continues producing buffers at the same rate. This could occur anytime when SurfaceFlinger does not meet the expected timeline’s deadline to finish composing a frame. As a result, SurfaceFlinger cannot yet release the buffer for the frame that it missed and the client has one less buffer to render into. The client may then run out of buffers or have to wait for buffer release callbacks, increasing the chances of janking when clients render multiple windows.
+
+Recovery is implemented by first detecting when buffer stuffing occurs and ensuring that the elevated buffer counts in the server are from a relevant SurfaceControl (is a ViewRootImpl). Other SurfaceControl buffer producers such as games, media, and camera have other reasons for expectedly increased buffer counts, which do not need buffer stuffing recovery.
+
+The actual recovery adjusts the animation timeline in the Choreographer so that the client deadlines for subsequent frames are moved forward in time by one frame. This approach adjusts the client buffer production timeline such that SurfaceFlinger does not fall behind when it misses a frame because the client will simply match its frame production rate with SurfaceFlinger. Ordinarily, buffer stuffing is problematic because the client continues producing buffers when SurfaceFlinger is behind. However, if the client delays producing its buffers to match SurfaceFlinger’s rate, the animation has new frame deadlines that can be reasonably met. The animation is effectively paused for one frame longer than originally intended, and continues the remainder of the animation normally. \ No newline at end of file
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 0c8f3fa096..b9e6c06425 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -69,7 +69,7 @@ namespace android {
Choreographer::Context Choreographer::gChoreographers;
-static thread_local Choreographer* gChoreographer;
+static thread_local sp<Choreographer> gChoreographer;
void Choreographer::initJVM(JNIEnv* env) {
env->GetJavaVM(&gJni.jvm);
@@ -86,14 +86,14 @@ void Choreographer::initJVM(JNIEnv* env) {
"()V");
}
-Choreographer* Choreographer::getForThread() {
+sp<Choreographer> Choreographer::getForThread() {
if (gChoreographer == nullptr) {
sp<Looper> looper = Looper::getForThread();
if (!looper.get()) {
ALOGW("No looper prepared for thread");
return nullptr;
}
- gChoreographer = new Choreographer(looper);
+ gChoreographer = sp<Choreographer>::make(looper);
status_t result = gChoreographer->initialize();
if (result != OK) {
ALOGW("Failed to initialize");
@@ -238,7 +238,7 @@ void Choreographer::scheduleLatestConfigRequest() {
// socket should be atomic across processes.
DisplayEventReceiver::Event event;
event.header =
- DisplayEventReceiver::Event::Header{DisplayEventReceiver::DISPLAY_EVENT_NULL,
+ DisplayEventReceiver::Event::Header{DisplayEventType::DISPLAY_EVENT_NULL,
PhysicalDisplayId::fromPort(0), systemTime()};
injectEvent(event);
}
@@ -369,6 +369,10 @@ void Choreographer::dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32
this, to_string(displayId).c_str(), connectedLevel, maxLevel);
}
+void Choreographer::dispatchModeRejected(PhysicalDisplayId, int32_t) {
+ LOG_ALWAYS_FATAL("dispatchModeRejected was called but was never registered");
+}
+
void Choreographer::handleMessage(const Message& message) {
switch (message.what) {
case MSG_SCHEDULE_CALLBACKS:
diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp
index 602bba8dab..0266a3f2e0 100644
--- a/libs/gui/ConsumerBase.cpp
+++ b/libs/gui/ConsumerBase.cpp
@@ -37,6 +37,8 @@
#include <private/gui/ComposerService.h>
+#include <ui/BufferQueueDefs.h>
+
#include <log/log.h>
#include <utils/Log.h>
#include <utils/String8.h>
@@ -59,7 +61,11 @@ static int32_t createProcessUniqueId() {
return android_atomic_inc(&globalCounter);
}
-ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp) :
+ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool controlledByApp)
+ :
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
mAbandoned(false),
mConsumer(bufferQueue),
mPrevFinalReleaseFence(Fence::NO_FENCE) {
@@ -68,7 +74,12 @@ ConsumerBase::ConsumerBase(const sp<IGraphicBufferConsumer>& bufferQueue, bool c
#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
ConsumerBase::ConsumerBase(bool controlledByApp, bool consumerIsSurfaceFlinger)
- : mAbandoned(false), mPrevFinalReleaseFence(Fence::NO_FENCE) {
+ :
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
+ mAbandoned(false),
+ mPrevFinalReleaseFence(Fence::NO_FENCE) {
sp<IGraphicBufferProducer> producer;
BufferQueue::createBufferQueue(&producer, &mConsumer, consumerIsSurfaceFlinger);
mSurface = sp<Surface>::make(producer, controlledByApp);
@@ -77,7 +88,11 @@ ConsumerBase::ConsumerBase(bool controlledByApp, bool consumerIsSurfaceFlinger)
ConsumerBase::ConsumerBase(const sp<IGraphicBufferProducer>& producer,
const sp<IGraphicBufferConsumer>& consumer, bool controlledByApp)
- : mAbandoned(false),
+ :
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
+ mAbandoned(false),
mConsumer(consumer),
mSurface(sp<Surface>::make(producer, controlledByApp)),
mPrevFinalReleaseFence(Fence::NO_FENCE) {
@@ -101,9 +116,16 @@ void ConsumerBase::initialize(bool controlledByApp) {
if (err != NO_ERROR) {
CB_LOGE("ConsumerBase: error connecting to BufferQueue: %s (%d)",
strerror(-err), err);
- } else {
- mConsumer->setConsumerName(mName);
+ return;
}
+
+ mConsumer->setConsumerName(mName);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ if (err = mConsumer->allowUnlimitedSlots(true); err != NO_ERROR) {
+ CB_LOGE("ConsumerBase: error marking as allowed to have unlimited slots: %s (%d)",
+ strerror(-err), err);
+ }
+#endif
}
ConsumerBase::~ConsumerBase() {
@@ -130,7 +152,11 @@ int ConsumerBase::getSlotForBufferLocked(const sp<GraphicBuffer>& buffer) {
}
uint64_t id = buffer->getId();
- for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ for (int i = 0; i < (int)mSlots.size(); ++i) {
+#else
+ for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+#endif
auto& slot = mSlots[i];
if (slot.mGraphicBuffer && slot.mGraphicBuffer->getId() == id) {
return i;
@@ -242,6 +268,15 @@ void ConsumerBase::onBuffersReleased() {
return;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ std::vector<bool> mask;
+ mConsumer->getReleasedBuffersExtended(&mask);
+ for (size_t i = 0; i < mSlots.size(); i++) {
+ if (mask[i]) {
+ freeBufferLocked(i);
+ }
+ }
+#else
uint64_t mask = 0;
mConsumer->getReleasedBuffers(&mask);
for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
@@ -249,11 +284,23 @@ void ConsumerBase::onBuffersReleased() {
freeBufferLocked(i);
}
}
+#endif
}
void ConsumerBase::onSidebandStreamChanged() {
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+void ConsumerBase::onSlotCountChanged(int slotCount) {
+ CB_LOGV("onSlotCountChanged: %d", slotCount);
+ Mutex::Autolock lock(mMutex);
+
+ if (slotCount > (int)mSlots.size()) {
+ mSlots.resize(slotCount);
+ }
+}
+#endif
+
void ConsumerBase::abandon() {
CB_LOGV("abandon");
Mutex::Autolock lock(mMutex);
@@ -270,7 +317,11 @@ void ConsumerBase::abandonLocked() {
CB_LOGE("abandonLocked: ConsumerBase is abandoned!");
return;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ for (int i = 0; i < (int)mSlots.size(); ++i) {
+#else
for (int i =0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+#endif
freeBufferLocked(i);
}
// disconnect from the BufferQueue
@@ -334,6 +385,26 @@ status_t ConsumerBase::detachBuffer(const sp<GraphicBuffer>& buffer) {
}
#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+status_t ConsumerBase::addReleaseFence(const sp<GraphicBuffer> buffer, const sp<Fence>& fence) {
+ CB_LOGV("addReleaseFence");
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ CB_LOGE("addReleaseFence: ConsumerBase is abandoned!");
+ return NO_INIT;
+ }
+ if (buffer == nullptr) {
+ return BAD_VALUE;
+ }
+
+ int slotIndex = getSlotForBufferLocked(buffer);
+ if (slotIndex == BufferQueue::INVALID_BUFFER_SLOT) {
+ return BAD_VALUE;
+ }
+
+ return addReleaseFenceLocked(slotIndex, buffer, fence);
+}
+
status_t ConsumerBase::setDefaultBufferSize(uint32_t width, uint32_t height) {
Mutex::Autolock _l(mMutex);
if (mAbandoned) {
@@ -387,6 +458,15 @@ status_t ConsumerBase::setMaxBufferCount(int bufferCount) {
CB_LOGE("setMaxBufferCount: ConsumerBase is abandoned!");
return NO_INIT;
}
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ if (status_t err = mConsumer->allowUnlimitedSlots(false); err != NO_ERROR) {
+ CB_LOGE("ConsumerBase: error marking as not allowed to have unlimited slots: %s (%d)",
+ strerror(-err), err);
+ return err;
+ }
+#endif
+
return mConsumer->setMaxBufferCount(bufferCount);
}
#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
@@ -400,7 +480,6 @@ status_t ConsumerBase::setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
return mConsumer->setMaxAcquiredBufferCount(maxAcquiredBuffers);
}
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
status_t ConsumerBase::setConsumerIsProtected(bool isProtected) {
Mutex::Autolock lock(mMutex);
if (mAbandoned) {
@@ -409,7 +488,6 @@ status_t ConsumerBase::setConsumerIsProtected(bool isProtected) {
}
return mConsumer->setConsumerIsProtected(isProtected);
}
-#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
sp<NativeHandle> ConsumerBase::getSidebandStream() const {
Mutex::Autolock _l(mMutex);
@@ -448,6 +526,15 @@ status_t ConsumerBase::discardFreeBuffers() {
if (err != OK) {
return err;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ std::vector<bool> mask;
+ mConsumer->getReleasedBuffersExtended(&mask);
+ for (int i = 0; i < (int)mSlots.size(); i++) {
+ if (mask[i]) {
+ freeBufferLocked(i);
+ }
+ }
+#else
uint64_t mask;
mConsumer->getReleasedBuffers(&mask);
for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
@@ -455,6 +542,8 @@ status_t ConsumerBase::discardFreeBuffers() {
freeBufferLocked(i);
}
}
+#endif
+
return OK;
}
@@ -585,9 +674,13 @@ status_t ConsumerBase::addReleaseFenceLocked(int slot,
return OK;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+status_t ConsumerBase::releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer) {
+#else
status_t ConsumerBase::releaseBufferLocked(
int slot, const sp<GraphicBuffer> graphicBuffer,
EGLDisplay display, EGLSyncKHR eglFence) {
+#endif
if (mAbandoned) {
CB_LOGE("releaseBufferLocked: ConsumerBase is abandoned!");
return NO_INIT;
@@ -596,13 +689,20 @@ status_t ConsumerBase::releaseBufferLocked(
// buffer on the same slot), the buffer producer is definitely no longer
// tracking it.
if (!stillTracking(slot, graphicBuffer)) {
+ CB_LOGV("releaseBufferLocked: Not tracking, exiting without calling releaseBuffer for "
+ "slot=%d/%" PRIu64,
+ slot, mSlots[slot].mFrameNumber);
return OK;
}
CB_LOGV("releaseBufferLocked: slot=%d/%" PRIu64,
slot, mSlots[slot].mFrameNumber);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber, mSlots[slot].mFence);
+#else
status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber,
display, eglFence, mSlots[slot].mFence);
+#endif
if (err == IGraphicBufferConsumer::STALE_BUFFER_SLOT) {
freeBufferLocked(slot);
}
@@ -615,7 +715,11 @@ status_t ConsumerBase::releaseBufferLocked(
bool ConsumerBase::stillTracking(int slot,
const sp<GraphicBuffer> graphicBuffer) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ if (slot < 0 || slot >= (int)mSlots.size()) {
+#else
if (slot < 0 || slot >= BufferQueue::NUM_BUFFER_SLOTS) {
+#endif
return false;
}
return (mSlots[slot].mGraphicBuffer != nullptr &&
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index c46f9c50ef..6f238859a4 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -167,7 +167,7 @@ bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp,
for (ssize_t i = 0; i < n; i++) {
const DisplayEventReceiver::Event& ev = buf[i];
switch (ev.header.type) {
- case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+ case DisplayEventType::DISPLAY_EVENT_VSYNC:
// Later vsync events will just overwrite the info from earlier
// ones. That's fine, we only care about the most recent.
gotVsync = true;
@@ -183,7 +183,7 @@ bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp,
ATRACE_INT("RenderRate", fps);
}
break;
- case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
+ case DisplayEventType::DISPLAY_EVENT_HOTPLUG:
if (ev.hotplug.connectionError == 0) {
dispatchHotplug(ev.header.timestamp, ev.header.displayId,
ev.hotplug.connected);
@@ -192,27 +192,27 @@ bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp,
ev.hotplug.connectionError);
}
break;
- case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
+ case DisplayEventType::DISPLAY_EVENT_MODE_CHANGE:
dispatchModeChanged(ev.header.timestamp, ev.header.displayId,
ev.modeChange.modeId, ev.modeChange.vsyncPeriod);
break;
- case DisplayEventReceiver::DISPLAY_EVENT_NULL:
+ case DisplayEventType::DISPLAY_EVENT_NULL:
dispatchNullEvent(ev.header.timestamp, ev.header.displayId);
break;
- case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
+ case DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
mFrameRateOverrides.emplace_back(ev.frameRateOverride);
break;
- case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH:
+ case DisplayEventType::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH:
dispatchFrameRateOverrides(ev.header.timestamp, ev.header.displayId,
std::move(mFrameRateOverrides));
break;
- case DisplayEventReceiver::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
+ case DisplayEventType::DISPLAY_EVENT_HDCP_LEVELS_CHANGE:
dispatchHdcpLevelsChanged(ev.header.displayId,
ev.hdcpLevelsChange.connectedLevel,
ev.hdcpLevelsChange.maxLevel);
break;
- default:
- ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type);
+ case DisplayEventType::DISPLAY_EVENT_MODE_REJECTION:
+ dispatchModeRejected(ev.header.displayId, ev.modeRejection.modeId);
break;
}
}
diff --git a/libs/gui/DisplayLuts.cpp b/libs/gui/DisplayLuts.cpp
new file mode 100644
index 0000000000..80429765be
--- /dev/null
+++ b/libs/gui/DisplayLuts.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 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 "include/gui/DisplayLuts.h"
+#include <gui/DisplayLuts.h>
+#include <private/gui/ParcelUtils.h>
+
+namespace android::gui {
+
+status_t DisplayLuts::Entry::readFromParcel(const android::Parcel* parcel) {
+ if (parcel == nullptr) {
+ ALOGE("%s: Null parcel", __func__);
+ return BAD_VALUE;
+ }
+
+ SAFE_PARCEL(parcel->readInt32, &dimension);
+ SAFE_PARCEL(parcel->readInt32, &size);
+ SAFE_PARCEL(parcel->readInt32, &samplingKey);
+
+ return OK;
+}
+
+status_t DisplayLuts::Entry::writeToParcel(android::Parcel* parcel) const {
+ if (parcel == nullptr) {
+ ALOGE("%s: Null parcel", __func__);
+ return BAD_VALUE;
+ }
+
+ SAFE_PARCEL(parcel->writeInt32, dimension);
+ SAFE_PARCEL(parcel->writeInt32, size);
+ SAFE_PARCEL(parcel->writeInt32, samplingKey);
+
+ return OK;
+}
+
+status_t DisplayLuts::readFromParcel(const android::Parcel* parcel) {
+ if (parcel == nullptr) {
+ ALOGE("%s: Null parcel", __func__);
+ return BAD_VALUE;
+ }
+
+ SAFE_PARCEL(parcel->readUniqueFileDescriptor, &fd);
+ SAFE_PARCEL(parcel->readInt32Vector, &offsets);
+ int32_t numLutProperties;
+ SAFE_PARCEL(parcel->readInt32, &numLutProperties);
+ lutProperties.reserve(numLutProperties);
+ for (int32_t i = 0; i < numLutProperties; i++) {
+ lutProperties.push_back({});
+ SAFE_PARCEL(lutProperties.back().readFromParcel, parcel);
+ }
+ return OK;
+}
+
+status_t DisplayLuts::writeToParcel(android::Parcel* parcel) const {
+ if (parcel == nullptr) {
+ ALOGE("%s: Null parcel", __func__);
+ return BAD_VALUE;
+ }
+
+ SAFE_PARCEL(parcel->writeUniqueFileDescriptor, fd);
+ SAFE_PARCEL(parcel->writeInt32Vector, offsets);
+ SAFE_PARCEL(parcel->writeInt32, static_cast<int32_t>(lutProperties.size()));
+ for (auto& entry : lutProperties) {
+ SAFE_PARCEL(entry.writeToParcel, parcel);
+ }
+ return OK;
+}
+} // namespace android::gui \ No newline at end of file
diff --git a/libs/gui/Flags.cpp b/libs/gui/Flags.cpp
new file mode 100644
index 0000000000..ee2802f706
--- /dev/null
+++ b/libs/gui/Flags.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 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 <gui/Flags.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/Surface.h>
+#include <gui/view/Surface.h>
+
+namespace android {
+namespace flagtools {
+sp<SurfaceType> surfaceToSurfaceType(const sp<Surface>& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ return surface;
+#else
+ return surface->getIGraphicBufferProducer();
+#endif
+}
+
+ParcelableSurfaceType surfaceToParcelableSurfaceType(const sp<Surface>& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ return view::Surface::fromSurface(surface);
+#else
+ return surface->getIGraphicBufferProducer();
+#endif
+}
+
+sp<IGraphicBufferProducer> surfaceTypeToIGBP(const sp<SurfaceType>& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ return surface->getIGraphicBufferProducer();
+#else
+ return surface;
+#endif
+}
+
+bool isSurfaceTypeValid(const sp<SurfaceType>& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ return Surface::isValid(surface);
+#else
+ return surface != nullptr;
+#endif
+}
+
+ParcelableSurfaceType toParcelableSurfaceType(const view::Surface& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ return surface;
+#else
+ return surface.graphicBufferProducer;
+#endif
+}
+
+ParcelableSurfaceType convertSurfaceTypeToParcelable(sp<SurfaceType> surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ return view::Surface::fromSurface(surface);
+#else
+ return surface;
+#endif
+}
+
+sp<SurfaceType> convertParcelableSurfaceTypeToSurface(const ParcelableSurfaceType& surface) {
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ return surface.toSurface();
+#else
+ return surface;
+#endif
+}
+
+} // namespace flagtools
+} // namespace android \ No newline at end of file
diff --git a/libs/gui/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
index 01aa7ed43c..1b2354e942 100644
--- a/libs/gui/FrameRateUtils.cpp
+++ b/libs/gui/FrameRateUtils.cpp
@@ -42,7 +42,7 @@ bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrame
if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
- compatibility != ANATIVEWINDOW_FRAME_RATE_GTE &&
+ compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST &&
(!privileged ||
(compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
diff --git a/libs/gui/GLConsumer.cpp b/libs/gui/GLConsumer.cpp
index 95cce5c1df..052b8edfaa 100644
--- a/libs/gui/GLConsumer.cpp
+++ b/libs/gui/GLConsumer.cpp
@@ -119,6 +119,9 @@ GLConsumer::GLConsumer(uint32_t tex, uint32_t texTarget, bool useFenceSync, bool
mTexTarget(texTarget),
mEglDisplay(EGL_NO_DISPLAY),
mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
mAttached(true) {
GLC_LOGV("GLConsumer");
@@ -129,27 +132,29 @@ GLConsumer::GLConsumer(uint32_t tex, uint32_t texTarget, bool useFenceSync, bool
}
#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
-GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t tex,
- uint32_t texTarget, bool useFenceSync, bool isControlledByApp) :
- ConsumerBase(bq, isControlledByApp),
- mCurrentCrop(Rect::EMPTY_RECT),
- mCurrentTransform(0),
- mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
- mCurrentFence(Fence::NO_FENCE),
- mCurrentTimestamp(0),
- mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
- mCurrentFrameNumber(0),
- mDefaultWidth(1),
- mDefaultHeight(1),
- mFilteringEnabled(true),
- mTexName(tex),
- mUseFenceSync(useFenceSync),
- mTexTarget(texTarget),
- mEglDisplay(EGL_NO_DISPLAY),
- mEglContext(EGL_NO_CONTEXT),
- mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
- mAttached(true)
-{
+GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t tex, uint32_t texTarget,
+ bool useFenceSync, bool isControlledByApp)
+ : ConsumerBase(bq, isControlledByApp),
+ mCurrentCrop(Rect::EMPTY_RECT),
+ mCurrentTransform(0),
+ mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mCurrentFence(Fence::NO_FENCE),
+ mCurrentTimestamp(0),
+ mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
+ mCurrentFrameNumber(0),
+ mDefaultWidth(1),
+ mDefaultHeight(1),
+ mFilteringEnabled(true),
+ mTexName(tex),
+ mUseFenceSync(useFenceSync),
+ mTexTarget(texTarget),
+ mEglDisplay(EGL_NO_DISPLAY),
+ mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
+ mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
+ mAttached(true) {
GLC_LOGV("GLConsumer");
memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(),
@@ -176,6 +181,9 @@ GLConsumer::GLConsumer(uint32_t texTarget, bool useFenceSync, bool isControlledB
mTexTarget(texTarget),
mEglDisplay(EGL_NO_DISPLAY),
mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
mAttached(false) {
GLC_LOGV("GLConsumer");
@@ -204,6 +212,9 @@ GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t texTarget,
mTexTarget(texTarget),
mEglDisplay(EGL_NO_DISPLAY),
mEglContext(EGL_NO_CONTEXT),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mEglSlots(BufferQueueDefs::NUM_BUFFER_SLOTS),
+#endif
mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
mAttached(false) {
GLC_LOGV("GLConsumer");
@@ -314,7 +325,7 @@ status_t GLConsumer::releaseTexImage() {
// so... basically, nothing more to do here.
}
- err = releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+ err = releaseBufferLocked(buf, mSlots[buf].mGraphicBuffer);
if (err < NO_ERROR) {
GLC_LOGE("releaseTexImage: failed to release buffer: %s (%d)",
strerror(-err), err);
@@ -395,18 +406,29 @@ status_t GLConsumer::acquireBufferLocked(BufferItem *item,
return NO_ERROR;
}
-status_t GLConsumer::releaseBufferLocked(int buf,
- sp<GraphicBuffer> graphicBuffer,
- EGLDisplay display, EGLSyncKHR eglFence) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+void GLConsumer::onSlotCountChanged(int slotCount) {
+ ConsumerBase::onSlotCountChanged(slotCount);
+
+ Mutex::Autolock lock(mMutex);
+ if (slotCount > (int)mEglSlots.size()) {
+ mEglSlots.resize(slotCount);
+ }
+}
+#endif
+
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+status_t GLConsumer::releaseBufferLocked(int buf, sp<GraphicBuffer> graphicBuffer,
+ EGLDisplay display, EGLSyncKHR eglFence) {
// release the buffer if it hasn't already been discarded by the
// BufferQueue. This can happen, for example, when the producer of this
// buffer has reallocated the original buffer slot after this buffer
// was acquired.
- status_t err = ConsumerBase::releaseBufferLocked(
- buf, graphicBuffer, display, eglFence);
+ status_t err = ConsumerBase::releaseBufferLocked(buf, graphicBuffer, display, eglFence);
mEglSlots[buf].mEglFence = EGL_NO_SYNC_KHR;
return err;
}
+#endif
status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item,
PendingRelease* pendingRelease)
@@ -418,16 +440,14 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item,
if (!mAttached) {
GLC_LOGE("updateAndRelease: GLConsumer is not attached to an OpenGL "
"ES context");
- releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
- mEglDisplay, EGL_NO_SYNC_KHR);
+ releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
return INVALID_OPERATION;
}
// Confirm state.
err = checkAndUpdateEglStateLocked();
if (err != NO_ERROR) {
- releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
- mEglDisplay, EGL_NO_SYNC_KHR);
+ releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
return err;
}
@@ -440,8 +460,7 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item,
if (err != NO_ERROR) {
GLC_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d",
mEglDisplay, slot);
- releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
- mEglDisplay, EGL_NO_SYNC_KHR);
+ releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
return UNKNOWN_ERROR;
}
@@ -453,8 +472,7 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item,
// release the old buffer, so instead we just drop the new frame.
// As we are still under lock since acquireBuffer, it is safe to
// release by slot.
- releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer,
- mEglDisplay, EGL_NO_SYNC_KHR);
+ releaseBufferLocked(slot, mSlots[slot].mGraphicBuffer);
return err;
}
}
@@ -472,9 +490,14 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item,
// release old buffer
if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
if (pendingRelease == nullptr) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ status_t status =
+ releaseBufferLocked(mCurrentTexture, mCurrentTextureImage->graphicBuffer());
+#else
status_t status = releaseBufferLocked(
mCurrentTexture, mCurrentTextureImage->graphicBuffer(),
mEglDisplay, mEglSlots[mCurrentTexture].mEglFence);
+#endif
if (status < NO_ERROR) {
GLC_LOGE("updateAndRelease: failed to release buffer: %s (%d)",
strerror(-status), status);
@@ -483,10 +506,7 @@ status_t GLConsumer::updateAndReleaseLocked(const BufferItem& item,
}
} else {
pendingRelease->currentTexture = mCurrentTexture;
- pendingRelease->graphicBuffer =
- mCurrentTextureImage->graphicBuffer();
- pendingRelease->display = mEglDisplay;
- pendingRelease->fence = mEglSlots[mCurrentTexture].mEglFence;
+ pendingRelease->graphicBuffer = mCurrentTextureImage->graphicBuffer();
pendingRelease->isPending = true;
}
}
@@ -726,6 +746,11 @@ status_t GLConsumer::syncForReleaseLocked(EGLDisplay dpy) {
return err;
}
} else if (mUseFenceSync && SyncFeatures::getInstance().useFenceSync()) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ // Basically all clients are using native fence syncs. If they aren't, we lose nothing
+ // by waiting here, because the alternative can cause deadlocks (b/339705065).
+ glFinish();
+#else
EGLSyncKHR fence = mEglSlots[mCurrentTexture].mEglFence;
if (fence != EGL_NO_SYNC_KHR) {
// There is already a fence for the current slot. We need to
@@ -755,6 +780,7 @@ status_t GLConsumer::syncForReleaseLocked(EGLDisplay dpy) {
}
glFlush();
mEglSlots[mCurrentTexture].mEglFence = fence;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
}
}
diff --git a/libs/gui/IConsumerListener.cpp b/libs/gui/IConsumerListener.cpp
deleted file mode 100644
index f3bd90cffb..0000000000
--- a/libs/gui/IConsumerListener.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2013 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 <gui/IConsumerListener.h>
-
-#include <gui/BufferItem.h>
-
-namespace android {
-
-namespace { // Anonymous
-
-enum class Tag : uint32_t {
- ON_DISCONNECT = IBinder::FIRST_CALL_TRANSACTION,
- ON_FRAME_AVAILABLE,
- ON_FRAME_REPLACED,
- ON_BUFFERS_RELEASED,
- ON_SIDEBAND_STREAM_CHANGED,
- ON_FRAME_DEQUEUED,
- ON_FRAME_CANCELLED,
- ON_FRAME_DETACHED,
- LAST = ON_FRAME_DETACHED,
-};
-
-} // Anonymous namespace
-
-class BpConsumerListener : public SafeBpInterface<IConsumerListener> {
-public:
- explicit BpConsumerListener(const sp<IBinder>& impl)
- : SafeBpInterface<IConsumerListener>(impl, "BpConsumerListener") {}
-
- ~BpConsumerListener() override;
-
- void onDisconnect() override {
- callRemoteAsync<decltype(&IConsumerListener::onDisconnect)>(Tag::ON_DISCONNECT);
- }
-
- void onFrameDequeued(const uint64_t bufferId) override {
- callRemoteAsync<decltype(&IConsumerListener::onFrameDequeued)>(Tag::ON_FRAME_DEQUEUED,
- bufferId);
- }
-
- void onFrameDetached(const uint64_t bufferId) override {
- callRemoteAsync<decltype(&IConsumerListener::onFrameDetached)>(Tag::ON_FRAME_DETACHED,
- bufferId);
- }
-
- void onFrameCancelled(const uint64_t bufferId) override {
- callRemoteAsync<decltype(&IConsumerListener::onFrameCancelled)>(Tag::ON_FRAME_CANCELLED,
- bufferId);
- }
-
- void onFrameAvailable(const BufferItem& item) override {
- callRemoteAsync<decltype(&IConsumerListener::onFrameAvailable)>(Tag::ON_FRAME_AVAILABLE,
- item);
- }
-
- void onFrameReplaced(const BufferItem& item) override {
- callRemoteAsync<decltype(&IConsumerListener::onFrameReplaced)>(Tag::ON_FRAME_REPLACED,
- item);
- }
-
- void onBuffersReleased() override {
- callRemoteAsync<decltype(&IConsumerListener::onBuffersReleased)>(Tag::ON_BUFFERS_RELEASED);
- }
-
- void onSidebandStreamChanged() override {
- callRemoteAsync<decltype(&IConsumerListener::onSidebandStreamChanged)>(
- Tag::ON_SIDEBAND_STREAM_CHANGED);
- }
-
- void addAndGetFrameTimestamps(const NewFrameEventsEntry* /*newTimestamps*/,
- FrameEventHistoryDelta* /*outDelta*/) override {
- LOG_ALWAYS_FATAL("IConsumerListener::addAndGetFrameTimestamps cannot be proxied");
- }
-};
-
-// Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see
-// clang warning -Wweak-vtables)
-BpConsumerListener::~BpConsumerListener() = default;
-
-IMPLEMENT_META_INTERFACE(ConsumerListener, "android.gui.IConsumerListener");
-
-status_t BnConsumerListener::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
- uint32_t flags) {
- if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast<uint32_t>(Tag::LAST)) {
- return BBinder::onTransact(code, data, reply, flags);
- }
- auto tag = static_cast<Tag>(code);
- switch (tag) {
- case Tag::ON_DISCONNECT:
- return callLocalAsync(data, reply, &IConsumerListener::onDisconnect);
- case Tag::ON_FRAME_AVAILABLE:
- return callLocalAsync(data, reply, &IConsumerListener::onFrameAvailable);
- case Tag::ON_FRAME_REPLACED:
- return callLocalAsync(data, reply, &IConsumerListener::onFrameReplaced);
- case Tag::ON_BUFFERS_RELEASED:
- return callLocalAsync(data, reply, &IConsumerListener::onBuffersReleased);
- case Tag::ON_SIDEBAND_STREAM_CHANGED:
- return callLocalAsync(data, reply, &IConsumerListener::onSidebandStreamChanged);
- case Tag::ON_FRAME_DEQUEUED:
- return callLocalAsync(data, reply, &IConsumerListener::onFrameDequeued);
- case Tag::ON_FRAME_CANCELLED:
- return callLocalAsync(data, reply, &IConsumerListener::onFrameCancelled);
- case Tag::ON_FRAME_DETACHED:
- return callLocalAsync(data, reply, &IConsumerListener::onFrameDetached);
- }
-}
-
-} // namespace android
diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp
deleted file mode 100644
index c705d3926d..0000000000
--- a/libs/gui/IGraphicBufferConsumer.cpp
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2013 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 <gui/IGraphicBufferConsumer.h>
-
-#include <gui/BufferItem.h>
-#include <gui/IConsumerListener.h>
-
-#include <binder/Parcel.h>
-
-#include <ui/Fence.h>
-#include <ui/GraphicBuffer.h>
-
-#include <utils/NativeHandle.h>
-#include <utils/String8.h>
-
-namespace android {
-
-namespace { // Anonymous namespace
-
-enum class Tag : uint32_t {
- ACQUIRE_BUFFER = IBinder::FIRST_CALL_TRANSACTION,
- DETACH_BUFFER,
- ATTACH_BUFFER,
- RELEASE_BUFFER,
- CONSUMER_CONNECT,
- CONSUMER_DISCONNECT,
- GET_RELEASED_BUFFERS,
- SET_DEFAULT_BUFFER_SIZE,
- SET_MAX_BUFFER_COUNT,
- SET_MAX_ACQUIRED_BUFFER_COUNT,
- SET_CONSUMER_NAME,
- SET_DEFAULT_BUFFER_FORMAT,
- SET_DEFAULT_BUFFER_DATA_SPACE,
- SET_CONSUMER_USAGE_BITS,
- SET_CONSUMER_IS_PROTECTED,
- SET_TRANSFORM_HINT,
- GET_SIDEBAND_STREAM,
- GET_OCCUPANCY_HISTORY,
- DISCARD_FREE_BUFFERS,
- DUMP_STATE,
- LAST = DUMP_STATE,
-};
-
-} // Anonymous namespace
-
-class BpGraphicBufferConsumer : public SafeBpInterface<IGraphicBufferConsumer> {
-public:
- explicit BpGraphicBufferConsumer(const sp<IBinder>& impl)
- : SafeBpInterface<IGraphicBufferConsumer>(impl, "BpGraphicBufferConsumer") {}
-
- ~BpGraphicBufferConsumer() override;
-
- status_t acquireBuffer(BufferItem* buffer, nsecs_t presentWhen,
- uint64_t maxFrameNumber) override {
- using Signature = decltype(&IGraphicBufferConsumer::acquireBuffer);
- return callRemote<Signature>(Tag::ACQUIRE_BUFFER, buffer, presentWhen, maxFrameNumber);
- }
-
- status_t detachBuffer(int slot) override {
- using Signature = decltype(&IGraphicBufferConsumer::detachBuffer);
- return callRemote<Signature>(Tag::DETACH_BUFFER, slot);
- }
-
- status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer) override {
- using Signature = decltype(&IGraphicBufferConsumer::attachBuffer);
- return callRemote<Signature>(Tag::ATTACH_BUFFER, slot, buffer);
- }
-
- status_t releaseBuffer(int buf, uint64_t frameNumber,
- EGLDisplay display __attribute__((unused)),
- EGLSyncKHR fence __attribute__((unused)),
- const sp<Fence>& releaseFence) override {
- return callRemote<ReleaseBuffer>(Tag::RELEASE_BUFFER, buf, frameNumber, releaseFence);
- }
-
- status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) override {
- using Signature = decltype(&IGraphicBufferConsumer::consumerConnect);
- return callRemote<Signature>(Tag::CONSUMER_CONNECT, consumer, controlledByApp);
- }
-
- status_t consumerDisconnect() override {
- return callRemote<decltype(&IGraphicBufferConsumer::consumerDisconnect)>(
- Tag::CONSUMER_DISCONNECT);
- }
-
- status_t getReleasedBuffers(uint64_t* slotMask) override {
- using Signature = decltype(&IGraphicBufferConsumer::getReleasedBuffers);
- return callRemote<Signature>(Tag::GET_RELEASED_BUFFERS, slotMask);
- }
-
- status_t setDefaultBufferSize(uint32_t width, uint32_t height) override {
- using Signature = decltype(&IGraphicBufferConsumer::setDefaultBufferSize);
- return callRemote<Signature>(Tag::SET_DEFAULT_BUFFER_SIZE, width, height);
- }
-
- status_t setMaxBufferCount(int bufferCount) override {
- using Signature = decltype(&IGraphicBufferConsumer::setMaxBufferCount);
- return callRemote<Signature>(Tag::SET_MAX_BUFFER_COUNT, bufferCount);
- }
-
- status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) override {
- using Signature = decltype(&IGraphicBufferConsumer::setMaxAcquiredBufferCount);
- return callRemote<Signature>(Tag::SET_MAX_ACQUIRED_BUFFER_COUNT, maxAcquiredBuffers);
- }
-
- status_t setConsumerName(const String8& name) override {
- using Signature = decltype(&IGraphicBufferConsumer::setConsumerName);
- return callRemote<Signature>(Tag::SET_CONSUMER_NAME, name);
- }
-
- status_t setDefaultBufferFormat(PixelFormat defaultFormat) override {
- using Signature = decltype(&IGraphicBufferConsumer::setDefaultBufferFormat);
- return callRemote<Signature>(Tag::SET_DEFAULT_BUFFER_FORMAT, defaultFormat);
- }
-
- status_t setDefaultBufferDataSpace(android_dataspace defaultDataSpace) override {
- using Signature = decltype(&IGraphicBufferConsumer::setDefaultBufferDataSpace);
- return callRemote<Signature>(Tag::SET_DEFAULT_BUFFER_DATA_SPACE, defaultDataSpace);
- }
-
- status_t setConsumerUsageBits(uint64_t usage) override {
- using Signature = decltype(&IGraphicBufferConsumer::setConsumerUsageBits);
- return callRemote<Signature>(Tag::SET_CONSUMER_USAGE_BITS, usage);
- }
-
- status_t setConsumerIsProtected(bool isProtected) override {
- using Signature = decltype(&IGraphicBufferConsumer::setConsumerIsProtected);
- return callRemote<Signature>(Tag::SET_CONSUMER_IS_PROTECTED, isProtected);
- }
-
- status_t setTransformHint(uint32_t hint) override {
- using Signature = decltype(&IGraphicBufferConsumer::setTransformHint);
- return callRemote<Signature>(Tag::SET_TRANSFORM_HINT, hint);
- }
-
- status_t getSidebandStream(sp<NativeHandle>* outStream) const override {
- using Signature = decltype(&IGraphicBufferConsumer::getSidebandStream);
- return callRemote<Signature>(Tag::GET_SIDEBAND_STREAM, outStream);
- }
-
- status_t getOccupancyHistory(bool forceFlush,
- std::vector<OccupancyTracker::Segment>* outHistory) override {
- using Signature = decltype(&IGraphicBufferConsumer::getOccupancyHistory);
- return callRemote<Signature>(Tag::GET_OCCUPANCY_HISTORY, forceFlush, outHistory);
- }
-
- status_t discardFreeBuffers() override {
- return callRemote<decltype(&IGraphicBufferConsumer::discardFreeBuffers)>(
- Tag::DISCARD_FREE_BUFFERS);
- }
-
- status_t dumpState(const String8& prefix, String8* outResult) const override {
- using Signature = status_t (IGraphicBufferConsumer::*)(const String8&, String8*) const;
- return callRemote<Signature>(Tag::DUMP_STATE, prefix, outResult);
- }
-};
-
-// Out-of-line virtual method definition to trigger vtable emission in this translation unit
-// (see clang warning -Wweak-vtables)
-BpGraphicBufferConsumer::~BpGraphicBufferConsumer() = default;
-
-IMPLEMENT_META_INTERFACE(GraphicBufferConsumer, "android.gui.IGraphicBufferConsumer");
-
-status_t BnGraphicBufferConsumer::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
- uint32_t flags) {
- if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast<uint32_t>(Tag::LAST)) {
- return BBinder::onTransact(code, data, reply, flags);
- }
- auto tag = static_cast<Tag>(code);
- switch (tag) {
- case Tag::ACQUIRE_BUFFER:
- return callLocal(data, reply, &IGraphicBufferConsumer::acquireBuffer);
- case Tag::DETACH_BUFFER:
- return callLocal(data, reply, &IGraphicBufferConsumer::detachBuffer);
- case Tag::ATTACH_BUFFER:
- return callLocal(data, reply, &IGraphicBufferConsumer::attachBuffer);
- case Tag::RELEASE_BUFFER:
- return callLocal(data, reply, &IGraphicBufferConsumer::releaseHelper);
- case Tag::CONSUMER_CONNECT:
- return callLocal(data, reply, &IGraphicBufferConsumer::consumerConnect);
- case Tag::CONSUMER_DISCONNECT:
- return callLocal(data, reply, &IGraphicBufferConsumer::consumerDisconnect);
- case Tag::GET_RELEASED_BUFFERS:
- return callLocal(data, reply, &IGraphicBufferConsumer::getReleasedBuffers);
- case Tag::SET_DEFAULT_BUFFER_SIZE:
- return callLocal(data, reply, &IGraphicBufferConsumer::setDefaultBufferSize);
- case Tag::SET_MAX_BUFFER_COUNT:
- return callLocal(data, reply, &IGraphicBufferConsumer::setMaxBufferCount);
- case Tag::SET_MAX_ACQUIRED_BUFFER_COUNT:
- return callLocal(data, reply, &IGraphicBufferConsumer::setMaxAcquiredBufferCount);
- case Tag::SET_CONSUMER_NAME:
- return callLocal(data, reply, &IGraphicBufferConsumer::setConsumerName);
- case Tag::SET_DEFAULT_BUFFER_FORMAT:
- return callLocal(data, reply, &IGraphicBufferConsumer::setDefaultBufferFormat);
- case Tag::SET_DEFAULT_BUFFER_DATA_SPACE:
- return callLocal(data, reply, &IGraphicBufferConsumer::setDefaultBufferDataSpace);
- case Tag::SET_CONSUMER_USAGE_BITS:
- return callLocal(data, reply, &IGraphicBufferConsumer::setConsumerUsageBits);
- case Tag::SET_CONSUMER_IS_PROTECTED:
- return callLocal(data, reply, &IGraphicBufferConsumer::setConsumerIsProtected);
- case Tag::SET_TRANSFORM_HINT:
- return callLocal(data, reply, &IGraphicBufferConsumer::setTransformHint);
- case Tag::GET_SIDEBAND_STREAM:
- return callLocal(data, reply, &IGraphicBufferConsumer::getSidebandStream);
- case Tag::GET_OCCUPANCY_HISTORY:
- return callLocal(data, reply, &IGraphicBufferConsumer::getOccupancyHistory);
- case Tag::DISCARD_FREE_BUFFERS:
- return callLocal(data, reply, &IGraphicBufferConsumer::discardFreeBuffers);
- case Tag::DUMP_STATE: {
- using Signature = status_t (IGraphicBufferConsumer::*)(const String8&, String8*) const;
- return callLocal<Signature>(data, reply, &IGraphicBufferConsumer::dumpState);
- }
- }
-}
-
-} // namespace android
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 09144806ee..9f71eb16e7 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -81,6 +81,7 @@ enum {
GET_LAST_QUEUED_BUFFER2,
SET_FRAME_RATE,
SET_ADDITIONAL_OPTIONS,
+ SET_MAX_BUFER_COUNT_EXTENDED,
};
class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -149,6 +150,20 @@ public:
return result;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ status_t extendSlotCount(int size) override {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeInt32(size);
+ status_t result = remote()->transact(SET_MAX_BUFER_COUNT_EXTENDED, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.readInt32();
+ return result;
+ }
+#endif
+
virtual status_t setAsyncMode(bool async) {
Parcel data, reply;
data.writeInterfaceToken(
@@ -981,6 +996,14 @@ IMPLEMENT_HYBRID_META_INTERFACE(GraphicBufferProducer,
// ----------------------------------------------------------------------
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+status_t IGraphicBufferProducer::extendSlotCount(int size) {
+ // No-op for IGBP other than BufferQueue.
+ (void)size;
+ return INVALID_OPERATION;
+}
+#endif
+
status_t IGraphicBufferProducer::setLegacyBufferDrop(bool drop) {
// No-op for IGBP other than BufferQueue.
(void) drop;
@@ -1582,6 +1605,15 @@ status_t BnGraphicBufferProducer::onTransact(
return NO_ERROR;
}
#endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ case SET_MAX_BUFER_COUNT_EXTENDED: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ int size = data.readInt32();
+ status_t result = extendSlotCount(size);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ }
+#endif
}
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/gui/IGraphicBufferProducerFlattenables.cpp b/libs/gui/IGraphicBufferProducerFlattenables.cpp
index c8b9b6751d..8b2e2ddc59 100644
--- a/libs/gui/IGraphicBufferProducerFlattenables.cpp
+++ b/libs/gui/IGraphicBufferProducerFlattenables.cpp
@@ -20,21 +20,19 @@
namespace android {
constexpr size_t IGraphicBufferProducer::QueueBufferInput::minFlattenedSize() {
- return sizeof(timestamp) +
- sizeof(isAutoTimestamp) +
- sizeof(dataSpace) +
- sizeof(crop) +
- sizeof(scalingMode) +
- sizeof(transform) +
- sizeof(stickyTransform) +
- sizeof(getFrameTimestamps) +
- sizeof(slot);
+ return sizeof(timestamp) + sizeof(isAutoTimestamp) + sizeof(dataSpace) + sizeof(crop) +
+ sizeof(scalingMode) + sizeof(transform) + sizeof(stickyTransform) +
+ sizeof(getFrameTimestamps) + sizeof(slot) +
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+ sizeof(decltype(pictureProfileHandle.has_value())) +
+ sizeof(decltype(pictureProfileHandle.getId()));
+#else
+ 0;
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
}
size_t IGraphicBufferProducer::QueueBufferInput::getFlattenedSize() const {
- return minFlattenedSize() +
- fence->getFlattenedSize() +
- surfaceDamage.getFlattenedSize() +
+ return minFlattenedSize() + fence->getFlattenedSize() + surfaceDamage.getFlattenedSize() +
hdrMetadata.getFlattenedSize();
}
@@ -57,6 +55,12 @@ status_t IGraphicBufferProducer::QueueBufferInput::flatten(
FlattenableUtils::write(buffer, size, transform);
FlattenableUtils::write(buffer, size, stickyTransform);
FlattenableUtils::write(buffer, size, getFrameTimestamps);
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+ FlattenableUtils::write(buffer, size, pictureProfileHandle.has_value());
+ FlattenableUtils::write(buffer, size,
+ pictureProfileHandle.has_value() ? pictureProfileHandle->getId()
+ : PictureProfileHandle::NONE.getId());
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
status_t result = fence->flatten(buffer, size, fds, count);
if (result != NO_ERROR) {
@@ -91,6 +95,15 @@ status_t IGraphicBufferProducer::QueueBufferInput::unflatten(
FlattenableUtils::read(buffer, size, transform);
FlattenableUtils::read(buffer, size, stickyTransform);
FlattenableUtils::read(buffer, size, getFrameTimestamps);
+#if COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
+ bool hasPictureProfileHandle;
+ FlattenableUtils::read(buffer, size, hasPictureProfileHandle);
+ PictureProfileId pictureProfileId;
+ FlattenableUtils::read(buffer, size, pictureProfileId);
+ pictureProfileHandle = hasPictureProfileHandle
+ ? std::optional(PictureProfileHandle(pictureProfileId))
+ : std::nullopt;
+#endif // COM_ANDROID_GRAPHICS_LIBUI_FLAGS_APPLY_PICTURE_PROFILES
fence = new Fence();
status_t result = fence->unflatten(buffer, size, fds, count);
@@ -115,7 +128,7 @@ status_t IGraphicBufferProducer::QueueBufferInput::unflatten(
constexpr size_t IGraphicBufferProducer::QueueBufferOutput::minFlattenedSize() {
return sizeof(width) + sizeof(height) + sizeof(transformHint) + sizeof(numPendingBuffers) +
sizeof(nextFrameNumber) + sizeof(bufferReplaced) + sizeof(maxBufferCount) +
- sizeof(result);
+ sizeof(result) + sizeof(isSlotExpansionAllowed);
}
size_t IGraphicBufferProducer::QueueBufferOutput::getFlattenedSize() const {
return minFlattenedSize() + frameTimestamps.getFlattenedSize();
@@ -139,6 +152,7 @@ status_t IGraphicBufferProducer::QueueBufferOutput::flatten(
FlattenableUtils::write(buffer, size, nextFrameNumber);
FlattenableUtils::write(buffer, size, bufferReplaced);
FlattenableUtils::write(buffer, size, maxBufferCount);
+ FlattenableUtils::write(buffer, size, isSlotExpansionAllowed);
status_t result = frameTimestamps.flatten(buffer, size, fds, count);
if (result != NO_ERROR) {
@@ -162,6 +176,7 @@ status_t IGraphicBufferProducer::QueueBufferOutput::unflatten(
FlattenableUtils::read(buffer, size, nextFrameNumber);
FlattenableUtils::read(buffer, size, bufferReplaced);
FlattenableUtils::read(buffer, size, maxBufferCount);
+ FlattenableUtils::read(buffer, size, isSlotExpansionAllowed);
status_t result = frameTimestamps.unflatten(buffer, size, fds, count);
if (result != NO_ERROR) {
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index b10996951b..ebfc62f33f 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -21,6 +21,7 @@
#include <android/gui/ISurfaceComposerClient.h>
#include <android/native_window.h>
#include <binder/Parcel.h>
+#include <com_android_graphics_libgui_flags.h>
#include <gui/FrameRateUtils.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/LayerState.h>
@@ -65,11 +66,12 @@ layer_state_t::layer_state_t()
mask(0),
reserved(0),
cornerRadius(0.0f),
+ clientDrawnCornerRadius(0.0f),
backgroundBlurRadius(0),
color(0),
bufferTransform(0),
transformToDisplayInverse(false),
- crop(Rect::INVALID_RECT),
+ crop({0, 0, -1, -1}),
dataspace(ui::Dataspace::UNKNOWN),
surfaceDamageRegion(),
api(-1),
@@ -91,7 +93,9 @@ layer_state_t::layer_state_t()
trustedOverlay(gui::TrustedOverlay::UNSET),
bufferCrop(Rect::INVALID_RECT),
destinationFrame(Rect::INVALID_RECT),
- dropInputMode(gui::DropInputMode::NONE) {
+ dropInputMode(gui::DropInputMode::NONE),
+ pictureProfileHandle(PictureProfileHandle::NONE),
+ appContentPriority(0) {
matrix.dsdx = matrix.dtdy = 1.0f;
matrix.dsdy = matrix.dtdx = 0.0f;
hdrMetadata.validTypes = 0;
@@ -109,7 +113,10 @@ status_t layer_state_t::write(Parcel& output) const
SAFE_PARCEL(output.writeUint32, flags);
SAFE_PARCEL(output.writeUint32, mask);
SAFE_PARCEL(matrix.write, output);
- SAFE_PARCEL(output.write, crop);
+ SAFE_PARCEL(output.writeFloat, crop.top);
+ SAFE_PARCEL(output.writeFloat, crop.left);
+ SAFE_PARCEL(output.writeFloat, crop.bottom);
+ SAFE_PARCEL(output.writeFloat, crop.right);
SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, relativeLayerSurfaceControl);
SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, parentSurfaceControlForChild);
SAFE_PARCEL(output.writeFloat, color.r);
@@ -134,6 +141,7 @@ status_t layer_state_t::write(Parcel& output) const
SAFE_PARCEL(output.write, colorTransform.asArray(), 16 * sizeof(float));
SAFE_PARCEL(output.writeFloat, cornerRadius);
+ SAFE_PARCEL(output.writeFloat, clientDrawnCornerRadius);
SAFE_PARCEL(output.writeUint32, backgroundBlurRadius);
SAFE_PARCEL(output.writeParcelable, metadata);
SAFE_PARCEL(output.writeFloat, bgColor.r);
@@ -199,6 +207,16 @@ status_t layer_state_t::write(Parcel& output) const
if (hasBufferReleaseChannel) {
SAFE_PARCEL(output.writeParcelable, *bufferReleaseChannel);
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_APPLY_PICTURE_PROFILES
+ SAFE_PARCEL(output.writeInt64, pictureProfileHandle.getId());
+ SAFE_PARCEL(output.writeInt32, appContentPriority);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_APPLY_PICTURE_PROFILES
+
+ const bool hasLuts = (luts != nullptr);
+ SAFE_PARCEL(output.writeBool, hasLuts);
+ if (hasLuts) {
+ SAFE_PARCEL(output.writeParcelable, *luts);
+ }
return NO_ERROR;
}
@@ -218,7 +236,10 @@ status_t layer_state_t::read(const Parcel& input)
SAFE_PARCEL(input.readUint32, &mask);
SAFE_PARCEL(matrix.read, input);
- SAFE_PARCEL(input.read, crop);
+ SAFE_PARCEL(input.readFloat, &crop.top);
+ SAFE_PARCEL(input.readFloat, &crop.left);
+ SAFE_PARCEL(input.readFloat, &crop.bottom);
+ SAFE_PARCEL(input.readFloat, &crop.right);
SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &relativeLayerSurfaceControl);
SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &parentSurfaceControlForChild);
@@ -255,6 +276,7 @@ status_t layer_state_t::read(const Parcel& input)
SAFE_PARCEL(input.read, &colorTransform, 16 * sizeof(float));
SAFE_PARCEL(input.readFloat, &cornerRadius);
+ SAFE_PARCEL(input.readFloat, &clientDrawnCornerRadius);
SAFE_PARCEL(input.readUint32, &backgroundBlurRadius);
SAFE_PARCEL(input.readParcelable, &metadata);
@@ -351,6 +373,21 @@ status_t layer_state_t::read(const Parcel& input)
bufferReleaseChannel = std::make_shared<gui::BufferReleaseChannel::ProducerEndpoint>();
SAFE_PARCEL(input.readParcelable, bufferReleaseChannel.get());
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_APPLY_PICTURE_PROFILES
+ int64_t pictureProfileId;
+ SAFE_PARCEL(input.readInt64, &pictureProfileId);
+ pictureProfileHandle = PictureProfileHandle(pictureProfileId);
+ SAFE_PARCEL(input.readInt32, &appContentPriority);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_APPLY_PICTURE_PROFILES
+
+ bool hasLuts;
+ SAFE_PARCEL(input.readBool, &hasLuts);
+ if (hasLuts) {
+ luts = std::make_shared<gui::DisplayLuts>();
+ SAFE_PARCEL(input.readParcelable, luts.get());
+ } else {
+ luts = nullptr;
+ }
return NO_ERROR;
}
@@ -562,6 +599,10 @@ void layer_state_t::merge(const layer_state_t& other) {
what |= eCornerRadiusChanged;
cornerRadius = other.cornerRadius;
}
+ if (other.what & eClientDrawnCornerRadiusChanged) {
+ what |= eClientDrawnCornerRadiusChanged;
+ clientDrawnCornerRadius = other.clientDrawnCornerRadius;
+ }
if (other.what & eBackgroundBlurRadiusChanged) {
what |= eBackgroundBlurRadiusChanged;
backgroundBlurRadius = other.backgroundBlurRadius;
@@ -658,6 +699,10 @@ void layer_state_t::merge(const layer_state_t& other) {
what |= eShadowRadiusChanged;
shadowRadius = other.shadowRadius;
}
+ if (other.what & eLutsChanged) {
+ what |= eLutsChanged;
+ luts = other.luts;
+ }
if (other.what & eDefaultFrameRateCompatibilityChanged) {
what |= eDefaultFrameRateCompatibilityChanged;
defaultFrameRateCompatibility = other.defaultFrameRateCompatibility;
@@ -735,6 +780,16 @@ void layer_state_t::merge(const layer_state_t& other) {
what |= eBufferReleaseChannelChanged;
bufferReleaseChannel = other.bufferReleaseChannel;
}
+ if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
+ if (other.what & ePictureProfileHandleChanged) {
+ what |= ePictureProfileHandleChanged;
+ pictureProfileHandle = other.pictureProfileHandle;
+ }
+ if (other.what & eAppContentPriorityChanged) {
+ what |= eAppContentPriorityChanged;
+ appContentPriority = other.appContentPriority;
+ }
+ }
if ((other.what & what) != other.what) {
ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
"other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64,
@@ -761,6 +816,7 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const {
}
CHECK_DIFF(diff, eLayerStackChanged, other, layerStack);
CHECK_DIFF(diff, eCornerRadiusChanged, other, cornerRadius);
+ CHECK_DIFF(diff, eClientDrawnCornerRadiusChanged, other, clientDrawnCornerRadius);
CHECK_DIFF(diff, eBackgroundBlurRadiusChanged, other, backgroundBlurRadius);
if (other.what & eBlurRegionsChanged) diff |= eBlurRegionsChanged;
if (other.what & eRelativeLayerChanged) {
@@ -815,6 +871,10 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const {
CHECK_DIFF(diff, eColorSpaceAgnosticChanged, other, colorSpaceAgnostic);
CHECK_DIFF(diff, eDimmingEnabledChanged, other, dimmingEnabled);
if (other.what & eBufferReleaseChannelChanged) diff |= eBufferReleaseChannelChanged;
+ if (other.what & eLutsChanged) diff |= eLutsChanged;
+ CHECK_DIFF(diff, ePictureProfileHandleChanged, other, pictureProfileHandle);
+ CHECK_DIFF(diff, eAppContentPriorityChanged, other, appContentPriority);
+
return diff;
}
diff --git a/libs/gui/StreamSplitter.cpp b/libs/gui/StreamSplitter.cpp
index 2f8e104ea0..653b91bcf6 100644
--- a/libs/gui/StreamSplitter.cpp
+++ b/libs/gui/StreamSplitter.cpp
@@ -234,8 +234,7 @@ void StreamSplitter::onBufferReleasedByOutput(
LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
"attaching buffer to input failed (%d)", status);
- status = mInput->releaseBuffer(consumerSlot, /* frameNumber */ 0,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, tracker->getMergedFence());
+ status = mInput->releaseBuffer(consumerSlot, /* frameNumber */ 0, tracker->getMergedFence());
LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
"releasing buffer to input failed (%d)", status);
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 66e7ddd915..ec23365e1f 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -38,6 +38,7 @@
#include <utils/NativeHandle.h>
#include <utils/Trace.h>
+#include <ui/BufferQueueDefs.h>
#include <ui/DynamicDisplayInfo.h>
#include <ui/Fence.h>
#include <ui/GraphicBuffer.h>
@@ -98,7 +99,10 @@ Surface::Surface(const sp<IGraphicBufferProducer>& bufferProducer, bool controll
: mGraphicBufferProducer(bufferProducer),
#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
mSurfaceDeathListener(nullptr),
-#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+#endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mSlots(NUM_BUFFER_SLOTS),
+#endif
mCrop(Rect::EMPTY_RECT),
mBufferAge(0),
mGenerationNumber(0),
@@ -192,7 +196,7 @@ void Surface::allocateBuffers() {
status_t Surface::allowAllocation(bool allowAllocation) {
return mGraphicBufferProducer->allowAllocation(allowAllocation);
}
-#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+#endif
status_t Surface::setGenerationNumber(uint32_t generation) {
status_t result = mGraphicBufferProducer->setGenerationNumber(generation);
@@ -658,7 +662,11 @@ int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
return result;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ if (buf < 0 || buf >= (int)mSlots.size()) {
+#else
if (buf < 0 || buf >= NUM_BUFFER_SLOTS) {
+#endif
ALOGE("dequeueBuffer: IGraphicBufferProducer returned invalid slot number %d", buf);
android_errorWriteLog(0x534e4554, "36991414"); // SafetyNet logging
return FAILED_TRANSACTION;
@@ -757,7 +765,11 @@ status_t Surface::detachBuffer(const sp<GraphicBuffer>& buffer) {
Mutex::Autolock lock(mMutex);
uint64_t bufferId = buffer->getId();
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ for (int slot = 0; slot < (int)mSlots.size(); ++slot) {
+#else
for (int slot = 0; slot < Surface::NUM_BUFFER_SLOTS; ++slot) {
+#endif
auto& bufferSlot = mSlots[slot];
if (bufferSlot.buffer != nullptr && bufferSlot.buffer->getId() == bufferId) {
bufferSlot.buffer = nullptr;
@@ -840,7 +852,11 @@ int Surface::dequeueBuffers(std::vector<BatchBuffer>* buffers) {
return output.result;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ if (output.slot < 0 || output.slot >= (int)mSlots.size()) {
+#else
if (output.slot < 0 || output.slot >= NUM_BUFFER_SLOTS) {
+#endif
mGraphicBufferProducer->cancelBuffers(cancelBufferInputs, &cancelBufferOutputs);
ALOGE("%s: IGraphicBufferProducer returned invalid slot number %d",
__FUNCTION__, output.slot);
@@ -1027,7 +1043,11 @@ int Surface::getSlotFromBufferLocked(
return BAD_VALUE;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
if (mSlots[i].buffer != nullptr &&
mSlots[i].buffer->handle == buffer->handle) {
return i;
@@ -2094,6 +2114,9 @@ int Surface::connect(int api, const sp<SurfaceListener>& listener, bool reportBu
mDefaultHeight = output.height;
mNextFrameNumber = output.nextFrameNumber;
mMaxBufferCount = output.maxBufferCount;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ mIsSlotExpansionAllowed = output.isSlotExpansionAllowed;
+#endif
// Ignore transform hint if sticky transform is set or transform to display inverse flag is
// set. Transform hint should be ignored if the client is expected to always submit buffers
@@ -2190,7 +2213,11 @@ int Surface::detachNextBuffer(sp<GraphicBuffer>* outBuffer,
*outFence = Fence::NO_FENCE;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
if (mSlots[i].buffer != nullptr &&
mSlots[i].buffer->getId() == buffer->getId()) {
if (mReportRemovedBuffers) {
@@ -2292,8 +2319,35 @@ int Surface::setMaxDequeuedBufferCount(int maxDequeuedBuffers) {
ALOGV("Surface::setMaxDequeuedBufferCount");
Mutex::Autolock lock(mMutex);
- status_t err = mGraphicBufferProducer->setMaxDequeuedBufferCount(
- maxDequeuedBuffers);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ if (maxDequeuedBuffers > BufferQueueDefs::NUM_BUFFER_SLOTS && !mIsSlotExpansionAllowed) {
+ return BAD_VALUE;
+ }
+
+ int minUndequeuedBuffers = 0;
+ status_t err = mGraphicBufferProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ &minUndequeuedBuffers);
+ if (err != OK) {
+ ALOGE("IGraphicBufferProducer::query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS) returned %s",
+ strerror(-err));
+ return err;
+ }
+
+ if (maxDequeuedBuffers > (int)mSlots.size()) {
+ int newSlotCount = minUndequeuedBuffers + maxDequeuedBuffers;
+ err = mGraphicBufferProducer->extendSlotCount(newSlotCount);
+ if (err != OK) {
+ ALOGE("IGraphicBufferProducer::extendSlotCount(%d) returned %s", newSlotCount,
+ strerror(-err));
+ return err;
+ }
+
+ mSlots.resize(newSlotCount);
+ }
+ err = mGraphicBufferProducer->setMaxDequeuedBufferCount(maxDequeuedBuffers);
+#else
+ status_t err = mGraphicBufferProducer->setMaxDequeuedBufferCount(maxDequeuedBuffers);
+#endif
ALOGE_IF(err, "IGraphicBufferProducer::setMaxDequeuedBufferCount(%d) "
"returned %s", maxDequeuedBuffers, strerror(-err));
@@ -2501,7 +2555,11 @@ void Surface::freeAllBuffers() {
ALOGE("%s: %zu buffers were freed while being dequeued!",
__FUNCTION__, mDequeuedSlots.size());
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
mSlots[i].buffer = nullptr;
}
}
@@ -2510,7 +2568,11 @@ status_t Surface::getAndFlushBuffersFromSlots(const std::vector<int32_t>& slots,
std::vector<sp<GraphicBuffer>>* outBuffers) {
ALOGV("Surface::getAndFlushBuffersFromSlots");
for (int32_t i : slots) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ if (i < 0 || i >= (int)mSlots.size()) {
+#else
if (i < 0 || i >= NUM_BUFFER_SLOTS) {
+#endif
ALOGE("%s: Invalid slotIndex: %d", __FUNCTION__, i);
return BAD_VALUE;
}
@@ -2670,7 +2732,11 @@ status_t Surface::lock(
newDirtyRegion.set(bounds);
mDirtyRegion.clear();
Mutex::Autolock lock(mMutex);
- for (size_t i=0 ; i<NUM_BUFFER_SLOTS ; i++) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ for (int i = 0; i < (int)mSlots.size(); i++) {
+#else
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+#endif
mSlots[i].dirtyRegion.clear();
}
}
@@ -2735,8 +2801,8 @@ status_t Surface::unlockAndPost()
bool Surface::waitForNextFrame(uint64_t lastFrame, nsecs_t timeout) {
Mutex::Autolock lock(mMutex);
- if (mNextFrameNumber > lastFrame) {
- return true;
+ if (mLastFrameNumber > lastFrame) {
+ return true;
}
return mQueueBufferCondition.waitRelative(mMutex, timeout) == OK;
}
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index df58df43be..df57b275c5 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -19,8 +19,7 @@
#include <semaphore.h>
#include <stdint.h>
#include <sys/types.h>
-
-#include <com_android_graphics_libgui_flags.h>
+#include <algorithm>
#include <android/gui/BnWindowInfosReportedListener.h>
#include <android/gui/DisplayState.h>
@@ -29,6 +28,8 @@
#include <android/gui/IWindowInfosListener.h>
#include <android/gui/TrustedPresentationThresholds.h>
#include <android/os/IInputConstants.h>
+#include <com_android_graphics_libgui_flags.h>
+#include <gui/DisplayLuts.h>
#include <gui/FrameRateUtils.h>
#include <gui/TraceUtils.h>
#include <utils/Errors.h>
@@ -56,6 +57,7 @@
#include <ui/DisplayMode.h>
#include <ui/DisplayState.h>
#include <ui/DynamicDisplayInfo.h>
+#include <ui/FrameRateCategoryRate.h>
#include <android-base/thread_annotations.h>
#include <gui/LayerStatePermissions.h>
@@ -90,6 +92,7 @@ int64_t generateId() {
}
constexpr int64_t INVALID_VSYNC = -1;
+const constexpr char* LOG_SURFACE_CONTROL_REGISTRY = "SurfaceControlRegistry";
} // namespace
@@ -827,9 +830,7 @@ SurfaceComposerClient::Transaction::Transaction() {
SurfaceComposerClient::Transaction::Transaction(const Transaction& other)
: mId(other.mId),
- mAnimation(other.mAnimation),
- mEarlyWakeupStart(other.mEarlyWakeupStart),
- mEarlyWakeupEnd(other.mEarlyWakeupEnd),
+ mFlags(other.mFlags),
mMayContainBuffer(other.mMayContainBuffer),
mDesiredPresentTime(other.mDesiredPresentTime),
mIsAutoTimestamp(other.mIsAutoTimestamp),
@@ -844,7 +845,7 @@ SurfaceComposerClient::Transaction::Transaction(const Transaction& other)
void SurfaceComposerClient::Transaction::sanitize(int pid, int uid) {
uint32_t permissions = LayerStatePermissions::getTransactionPermissions(pid, uid);
- for (auto & [handle, composerState] : mComposerStates) {
+ for (auto& composerState : mComposerStates) {
composerState.state.sanitize(permissions);
}
if (!mInputWindowCommands.empty() &&
@@ -866,11 +867,10 @@ SurfaceComposerClient::Transaction::createFromParcel(const Parcel* parcel) {
status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel) {
const uint64_t transactionId = parcel->readUint64();
- const bool animation = parcel->readBool();
- const bool earlyWakeupStart = parcel->readBool();
- const bool earlyWakeupEnd = parcel->readBool();
+ const uint32_t flags = parcel->readUint32();
const int64_t desiredPresentTime = parcel->readInt64();
const bool isAutoTimestamp = parcel->readBool();
+ const bool logCallPoints = parcel->readBool();
FrameTimelineInfo frameTimelineInfo;
frameTimelineInfo.readFromParcel(parcel);
@@ -880,7 +880,7 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel
if (count > parcel->dataSize()) {
return BAD_VALUE;
}
- SortedVector<DisplayState> displayStates;
+ Vector<DisplayState> displayStates;
displayStates.setCapacity(count);
for (size_t i = 0; i < count; i++) {
DisplayState displayState;
@@ -923,17 +923,14 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel
if (count > parcel->dataSize()) {
return BAD_VALUE;
}
- std::unordered_map<sp<IBinder>, ComposerState, IBinderHash> composerStates;
- composerStates.reserve(count);
+ Vector<ComposerState> composerStates;
+ composerStates.setCapacity(count);
for (size_t i = 0; i < count; i++) {
- sp<IBinder> surfaceControlHandle;
- SAFE_PARCEL(parcel->readStrongBinder, &surfaceControlHandle);
-
ComposerState composerState;
if (composerState.read(*parcel) == BAD_VALUE) {
return BAD_VALUE;
}
- composerStates[surfaceControlHandle] = composerState;
+ composerStates.add(composerState);
}
InputWindowCommands inputWindowCommands;
@@ -962,15 +959,13 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel
// Parsing was successful. Update the object.
mId = transactionId;
- mAnimation = animation;
- mEarlyWakeupStart = earlyWakeupStart;
- mEarlyWakeupEnd = earlyWakeupEnd;
+ mFlags = flags;
mDesiredPresentTime = desiredPresentTime;
mIsAutoTimestamp = isAutoTimestamp;
mFrameTimelineInfo = frameTimelineInfo;
- mDisplayStates = displayStates;
+ mDisplayStates = std::move(displayStates);
mListenerCallbacks = listenerCallbacks;
- mComposerStates = composerStates;
+ mComposerStates = std::move(composerStates);
mInputWindowCommands = inputWindowCommands;
mApplyToken = applyToken;
mUncacheBuffers = std::move(uncacheBuffers);
@@ -993,11 +988,10 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const
const_cast<SurfaceComposerClient::Transaction*>(this)->cacheBuffers();
parcel->writeUint64(mId);
- parcel->writeBool(mAnimation);
- parcel->writeBool(mEarlyWakeupStart);
- parcel->writeBool(mEarlyWakeupEnd);
+ parcel->writeUint32(mFlags);
parcel->writeInt64(mDesiredPresentTime);
parcel->writeBool(mIsAutoTimestamp);
+ parcel->writeBool(mLogCallPoints);
mFrameTimelineInfo.writeToParcel(parcel);
parcel->writeStrongBinder(mApplyToken);
parcel->writeUint32(static_cast<uint32_t>(mDisplayStates.size()));
@@ -1019,8 +1013,7 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const
}
parcel->writeUint32(static_cast<uint32_t>(mComposerStates.size()));
- for (auto const& [handle, composerState] : mComposerStates) {
- SAFE_PARCEL(parcel->writeStrongBinder, handle);
+ for (auto const& composerState : mComposerStates) {
composerState.write(*parcel);
}
@@ -1077,23 +1070,31 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr
}
mMergedTransactionIds.insert(mMergedTransactionIds.begin(), other.mId);
- for (auto const& [handle, composerState] : other.mComposerStates) {
- if (mComposerStates.count(handle) == 0) {
- mComposerStates[handle] = composerState;
- } else {
- if (composerState.state.what & layer_state_t::eBufferChanged) {
- releaseBufferIfOverwriting(mComposerStates[handle].state);
+ for (auto const& otherState : other.mComposerStates) {
+ if (auto it = std::find_if(mComposerStates.begin(), mComposerStates.end(),
+ [&otherState](const auto& composerState) {
+ return composerState.state.surface ==
+ otherState.state.surface;
+ });
+ it != mComposerStates.end()) {
+ if (otherState.state.what & layer_state_t::eBufferChanged) {
+ releaseBufferIfOverwriting(it->state);
}
- mComposerStates[handle].state.merge(composerState.state);
+ it->state.merge(otherState.state);
+ } else {
+ mComposerStates.add(otherState);
}
}
for (auto const& state : other.mDisplayStates) {
- ssize_t index = mDisplayStates.indexOf(state);
- if (index < 0) {
- mDisplayStates.add(state);
+ if (auto it = std::find_if(mDisplayStates.begin(), mDisplayStates.end(),
+ [&state](const auto& displayState) {
+ return displayState.token == state.token;
+ });
+ it != mDisplayStates.end()) {
+ it->merge(state);
} else {
- mDisplayStates.editItemAt(static_cast<size_t>(index)).merge(state);
+ mDisplayStates.add(state);
}
}
@@ -1127,12 +1128,17 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Tr
mInputWindowCommands.merge(other.mInputWindowCommands);
mMayContainBuffer |= other.mMayContainBuffer;
- mEarlyWakeupStart = mEarlyWakeupStart || other.mEarlyWakeupStart;
- mEarlyWakeupEnd = mEarlyWakeupEnd || other.mEarlyWakeupEnd;
+ mFlags |= other.mFlags;
mApplyToken = other.mApplyToken;
mergeFrameTimelineInfo(mFrameTimelineInfo, other.mFrameTimelineInfo);
+ mLogCallPoints |= other.mLogCallPoints;
+ if (mLogCallPoints) {
+ ALOG(LOG_DEBUG, LOG_SURFACE_CONTROL_REGISTRY,
+ "Transaction %" PRIu64 " merged with transaction %" PRIu64, other.getId(), mId);
+ }
+
other.clear();
return *this;
}
@@ -1144,14 +1150,13 @@ void SurfaceComposerClient::Transaction::clear() {
mInputWindowCommands.clear();
mUncacheBuffers.clear();
mMayContainBuffer = false;
- mAnimation = false;
- mEarlyWakeupStart = false;
- mEarlyWakeupEnd = false;
mDesiredPresentTime = 0;
mIsAutoTimestamp = true;
mFrameTimelineInfo = {};
mApplyToken = nullptr;
mMergedTransactionIds.clear();
+ mLogCallPoints = false;
+ mFlags = 0;
}
uint64_t SurfaceComposerClient::Transaction::getId() {
@@ -1186,8 +1191,8 @@ void SurfaceComposerClient::Transaction::cacheBuffers() {
}
size_t count = 0;
- for (auto& [handle, cs] : mComposerStates) {
- layer_state_t* s = &(mComposerStates[handle].state);
+ for (auto& cs : mComposerStates) {
+ layer_state_t* s = &cs.state;
if (!(s->what & layer_state_t::eBufferChanged)) {
continue;
} else if (s->bufferData &&
@@ -1312,55 +1317,44 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay
cacheBuffers();
- Vector<ComposerState> composerStates;
- Vector<DisplayState> displayStates;
- uint32_t flags = 0;
-
- for (auto const& kv : mComposerStates) {
- composerStates.add(kv.second);
- }
-
- displayStates = std::move(mDisplayStates);
-
- if (mAnimation) {
- flags |= ISurfaceComposer::eAnimation;
- }
if (oneWay) {
if (synchronous) {
ALOGE("Transaction attempted to set synchronous and one way at the same time"
" this is an invalid request. Synchronous will win for safety");
} else {
- flags |= ISurfaceComposer::eOneWay;
+ mFlags |= ISurfaceComposer::eOneWay;
}
}
- // If both mEarlyWakeupStart and mEarlyWakeupEnd are set
+ // If both ISurfaceComposer::eEarlyWakeupStart and ISurfaceComposer::eEarlyWakeupEnd are set
// it is equivalent for none
- if (mEarlyWakeupStart && !mEarlyWakeupEnd) {
- flags |= ISurfaceComposer::eEarlyWakeupStart;
- }
- if (mEarlyWakeupEnd && !mEarlyWakeupStart) {
- flags |= ISurfaceComposer::eEarlyWakeupEnd;
+ uint32_t wakeupFlags = ISurfaceComposer::eEarlyWakeupStart | ISurfaceComposer::eEarlyWakeupEnd;
+ if ((mFlags & wakeupFlags) == wakeupFlags) {
+ mFlags &= ~(wakeupFlags);
}
-
sp<IBinder> applyToken = mApplyToken ? mApplyToken : getDefaultApplyToken();
sp<ISurfaceComposer> sf(ComposerService::getComposerService());
- sf->setTransactionState(mFrameTimelineInfo, composerStates, displayStates, flags, applyToken,
- mInputWindowCommands, mDesiredPresentTime, mIsAutoTimestamp,
- mUncacheBuffers, hasListenerCallbacks, listenerCallbacks, mId,
- mMergedTransactionIds);
+ status_t binderStatus =
+ sf->setTransactionState(mFrameTimelineInfo, mComposerStates, mDisplayStates, mFlags,
+ applyToken, mInputWindowCommands, mDesiredPresentTime,
+ mIsAutoTimestamp, mUncacheBuffers, hasListenerCallbacks,
+ listenerCallbacks, mId, mMergedTransactionIds);
mId = generateId();
// Clear the current states and flags
clear();
- if (synchronous) {
+ if (synchronous && binderStatus == OK) {
syncCallback->wait();
}
+ if (mLogCallPoints) {
+ ALOG(LOG_DEBUG, LOG_SURFACE_CONTROL_REGISTRY, "Transaction %" PRIu64 " applied", mId);
+ }
+
mStatus = NO_ERROR;
- return NO_ERROR;
+ return binderStatus;
}
sp<IBinder> SurfaceComposerClient::Transaction::sApplyToken = new BBinder();
@@ -1374,7 +1368,7 @@ sp<IBinder> SurfaceComposerClient::Transaction::getDefaultApplyToken() {
void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp<IBinder> applyToken) {
std::scoped_lock lock{sApplyTokenMutex};
- sApplyToken = applyToken;
+ sApplyToken = std::move(applyToken);
}
status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction(
@@ -1389,6 +1383,11 @@ status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction
t.registerSurfaceControlForCallback(sc);
return t.apply(/*sync=*/false, /* oneWay=*/true);
}
+
+void SurfaceComposerClient::Transaction::enableDebugLogCallPoints() {
+ mLogCallPoints = true;
+}
+
// ---------------------------------------------------------------------------
sp<IBinder> SurfaceComposerClient::createVirtualDisplay(const std::string& displayName,
@@ -1416,9 +1415,8 @@ std::vector<PhysicalDisplayId> SurfaceComposerClient::getPhysicalDisplayIds() {
ComposerServiceAIDL::getComposerService()->getPhysicalDisplayIds(&displayIds);
if (status.isOk()) {
physicalDisplayIds.reserve(displayIds.size());
- for (auto item : displayIds) {
- auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(item));
- physicalDisplayIds.push_back(*id);
+ for (auto id : displayIds) {
+ physicalDisplayIds.push_back(PhysicalDisplayId::fromValue(static_cast<uint64_t>(id)));
}
}
return physicalDisplayIds;
@@ -1440,31 +1438,34 @@ std::optional<gui::StalledTransactionInfo> SurfaceComposerClient::getStalledTran
}
void SurfaceComposerClient::Transaction::setAnimationTransaction() {
- mAnimation = true;
+ mFlags |= ISurfaceComposer::eAnimation;
}
void SurfaceComposerClient::Transaction::setEarlyWakeupStart() {
- mEarlyWakeupStart = true;
+ mFlags |= ISurfaceComposer::eEarlyWakeupStart;
}
void SurfaceComposerClient::Transaction::setEarlyWakeupEnd() {
- mEarlyWakeupEnd = true;
+ mFlags |= ISurfaceComposer::eEarlyWakeupEnd;
}
layer_state_t* SurfaceComposerClient::Transaction::getLayerState(const sp<SurfaceControl>& sc) {
auto handle = sc->getLayerStateHandle();
-
- if (mComposerStates.count(handle) == 0) {
- // we don't have it, add an initialized layer_state to our list
- ComposerState s;
-
- s.state.surface = handle;
- s.state.layerId = sc->getLayerId();
-
- mComposerStates[handle] = s;
+ if (auto it = std::find_if(mComposerStates.begin(), mComposerStates.end(),
+ [&handle](const auto& composerState) {
+ return composerState.state.surface == handle;
+ });
+ it != mComposerStates.end()) {
+ return &it->state;
}
- return &(mComposerStates[handle].state);
+ // we don't have it, add an initialized layer_state to our list
+ ComposerState s;
+ s.state.surface = handle;
+ s.state.layerId = sc->getLayerId();
+ mComposerStates.add(s);
+
+ return &mComposerStates.editItemAt(mComposerStates.size() - 1).state;
}
void SurfaceComposerClient::Transaction::registerSurfaceControlForCallback(
@@ -1539,14 +1540,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFlags
mStatus = BAD_INDEX;
return *this;
}
- if ((mask & layer_state_t::eLayerOpaque) || (mask & layer_state_t::eLayerHidden) ||
- (mask & layer_state_t::eLayerSecure) || (mask & layer_state_t::eLayerSkipScreenshot) ||
- (mask & layer_state_t::eEnableBackpressure) ||
- (mask & layer_state_t::eIgnoreDestinationFrame) ||
- (mask & layer_state_t::eLayerIsDisplayDecoration) ||
- (mask & layer_state_t::eLayerIsRefreshRateIndicator)) {
- s->what |= layer_state_t::eFlagsChanged;
- }
+ s->what |= layer_state_t::eFlagsChanged;
s->flags &= ~mask;
s->flags |= (flags & mask);
s->mask |= mask;
@@ -1652,6 +1646,11 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setMatri
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCrop(
const sp<SurfaceControl>& sc, const Rect& crop) {
+ return setCrop(sc, crop.toFloatRect());
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCrop(
+ const sp<SurfaceControl>& sc, const FloatRect& crop) {
layer_state_t* s = getLayerState(sc);
if (!s) {
mStatus = BAD_INDEX;
@@ -1676,6 +1675,18 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCorne
return *this;
}
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setClientDrawnCornerRadius(
+ const sp<SurfaceControl>& sc, float clientDrawnCornerRadius) {
+ layer_state_t* s = getLayerState(sc);
+ if (!s) {
+ mStatus = BAD_INDEX;
+ return *this;
+ }
+ s->what |= layer_state_t::eClientDrawnCornerRadiusChanged;
+ s->clientDrawnCornerRadius = clientDrawnCornerRadius;
+ return *this;
+}
+
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBackgroundBlurRadius(
const sp<SurfaceControl>& sc, int backgroundBlurRadius) {
layer_state_t* s = getLayerState(sc);
@@ -1941,6 +1952,28 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDesir
return *this;
}
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setLuts(
+ const sp<SurfaceControl>& sc, base::unique_fd&& lutFd, const std::vector<int32_t>& offsets,
+ const std::vector<int32_t>& dimensions, const std::vector<int32_t>& sizes,
+ const std::vector<int32_t>& samplingKeys) {
+ layer_state_t* s = getLayerState(sc);
+ if (!s) {
+ mStatus = BAD_INDEX;
+ return *this;
+ }
+
+ s->what |= layer_state_t::eLutsChanged;
+ if (lutFd.ok()) {
+ s->luts = std::make_shared<gui::DisplayLuts>(std::move(lutFd), offsets, dimensions, sizes,
+ samplingKeys);
+ } else {
+ s->luts = nullptr;
+ }
+
+ registerSurfaceControlForCallback(sc);
+ return *this;
+}
+
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCachingHint(
const sp<SurfaceControl>& sc, gui::CachingHint cachingHint) {
layer_state_t* s = getLayerState(sc);
@@ -2091,13 +2124,13 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::notifyPr
}
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setInputWindowInfo(
- const sp<SurfaceControl>& sc, const WindowInfo& info) {
+ const sp<SurfaceControl>& sc, sp<WindowInfoHandle> info) {
layer_state_t* s = getLayerState(sc);
if (!s) {
mStatus = BAD_INDEX;
return *this;
}
- s->windowInfoHandle = new WindowInfoHandle(info);
+ s->windowInfoHandle = std::move(info);
s->what |= layer_state_t::eInputInfoChanged;
return *this;
}
@@ -2409,18 +2442,54 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe
return *this;
}
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPictureProfileHandle(
+ const sp<SurfaceControl>& sc, const PictureProfileHandle& pictureProfileHandle) {
+ if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
+ layer_state_t* s = getLayerState(sc);
+ if (!s) {
+ mStatus = BAD_INDEX;
+ return *this;
+ }
+
+ s->what |= layer_state_t::ePictureProfileHandleChanged;
+ s->pictureProfileHandle = pictureProfileHandle;
+
+ registerSurfaceControlForCallback(sc);
+ }
+ return *this;
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setContentPriority(
+ const sp<SurfaceControl>& sc, int32_t priority) {
+ if (com_android_graphics_libgui_flags_apply_picture_profiles()) {
+ layer_state_t* s = getLayerState(sc);
+ if (!s) {
+ mStatus = BAD_INDEX;
+ return *this;
+ }
+
+ s->what |= layer_state_t::eAppContentPriorityChanged;
+ s->appContentPriority = priority;
+
+ registerSurfaceControlForCallback(sc);
+ }
+ return *this;
+}
+
// ---------------------------------------------------------------------------
DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) {
+ if (auto it = std::find_if(mDisplayStates.begin(), mDisplayStates.end(),
+ [token](const auto& display) { return display.token == token; });
+ it != mDisplayStates.end()) {
+ return *it;
+ }
+
+ // If display state doesn't exist, add a new one.
DisplayState s;
s.token = token;
- ssize_t index = mDisplayStates.indexOf(s);
- if (index < 0) {
- // we don't have it, add an initialized layer_state to our list
- s.what = 0;
- index = mDisplayStates.add(s);
- }
- return mDisplayStates.editItemAt(static_cast<size_t>(index));
+ mDisplayStates.add(s);
+ return mDisplayStates.editItemAt(mDisplayStates.size() - 1);
}
status_t SurfaceComposerClient::Transaction::setDisplaySurface(const sp<IBinder>& token,
@@ -2795,6 +2864,14 @@ void SurfaceComposerClient::getDynamicDisplayInfoInternal(gui::DynamicDisplayInf
outInfo->autoLowLatencyModeSupported = ginfo.autoLowLatencyModeSupported;
outInfo->gameContentTypeSupported = ginfo.gameContentTypeSupported;
outInfo->preferredBootDisplayMode = ginfo.preferredBootDisplayMode;
+ outInfo->hasArrSupport = ginfo.hasArrSupport;
+ outInfo->frameRateCategoryRate = ui::FrameRateCategoryRate(ginfo.frameRateCategoryRate.normal,
+ ginfo.frameRateCategoryRate.high);
+ outInfo->supportedRefreshRates.clear();
+ outInfo->supportedRefreshRates.reserve(ginfo.supportedRefreshRates.size());
+ for (const auto rate : ginfo.supportedRefreshRates) {
+ outInfo->supportedRefreshRates.push_back(static_cast<float>(rate));
+ }
}
status_t SurfaceComposerClient::getDynamicDisplayInfoFromId(int64_t displayId,
@@ -2978,6 +3055,14 @@ void SurfaceComposerClient::setDisplayPowerMode(const sp<IBinder>& token,
ComposerServiceAIDL::getComposerService()->setPowerMode(token, mode);
}
+status_t SurfaceComposerClient::getMaxLayerPictureProfiles(const sp<IBinder>& token,
+ int32_t* outMaxProfiles) {
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->getMaxLayerPictureProfiles(token,
+ outMaxProfiles);
+ return statusTFromBinderStatus(status);
+}
+
status_t SurfaceComposerClient::getCompositionPreference(
ui::Dataspace* defaultDataspace, ui::PixelFormat* defaultPixelFormat,
ui::Dataspace* wideColorGamutDataspace, ui::PixelFormat* wideColorGamutPixelFormat) {
@@ -3202,6 +3287,20 @@ status_t SurfaceComposerClient::removeHdrLayerInfoListener(
return statusTFromBinderStatus(status);
}
+status_t SurfaceComposerClient::addActivePictureListener(
+ const sp<gui::IActivePictureListener>& listener) {
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->addActivePictureListener(listener);
+ return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::removeActivePictureListener(
+ const sp<gui::IActivePictureListener>& listener) {
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->removeActivePictureListener(listener);
+ return statusTFromBinderStatus(status);
+}
+
status_t SurfaceComposerClient::notifyPowerBoost(int32_t boostId) {
binder::Status status = ComposerServiceAIDL::getComposerService()->notifyPowerBoost(boostId);
return statusTFromBinderStatus(status);
diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp
index f126c0be2f..b735418d4b 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -141,7 +141,8 @@ sp<Surface> SurfaceControl::generateSurfaceLocked()
ISurfaceComposerClient::eOpaque);
mBbqChild = mClient->createSurface(String8::format("[BBQ] %s", mName.c_str()), 0, 0, mFormat,
flags, mHandle, {}, &ignore);
- mBbq = sp<BLASTBufferQueue>::make("[BBQ]" + mName, mBbqChild, mWidth, mHeight, mFormat);
+ mBbq = sp<BLASTBufferQueue>::make("[BBQ] " + mName, /* updateDestinationFrame */ true);
+ mBbq->update(mBbqChild, mWidth, mHeight, mFormat);
// This surface is always consumed by SurfaceFlinger, so the
// producerControlledByApp value doesn't matter; using false.
diff --git a/libs/gui/TEST_MAPPING b/libs/gui/TEST_MAPPING
index a590c86ceb..14d6df62cb 100644
--- a/libs/gui/TEST_MAPPING
+++ b/libs/gui/TEST_MAPPING
@@ -60,5 +60,34 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "libgui_test",
+ "keywords": [ "primary-device" ],
+ "options": [
+ // TODO(b/397776630): Failing on real devices.
+ {
+ "exclude-filter": "InputSurfacesTest#input_respects_scaled_touchable_region_overflow"
+ },
+ // TODO(b/233363648): Failing on real devices.
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferNpot"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferPow2"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferWithCrop"
+ },
+ // TODO(b/233363648): Flaky on real devices.
+ {
+ "exclude-filter": "SurfaceTextureGLToGLTest#EglMakeCurrentBeforeConsumerDeathUnrefsBuffers"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLToGLTest#EglMakeCurrentAfterConsumerDeathUnrefsBuffers"
+ }
+ ]
+ }
]
}
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 82d2554340..3fb66d1f33 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -59,6 +59,32 @@ std::ostream& operator<<(std::ostream& out, const Region& region) {
return out;
}
+status_t writeTransform(android::Parcel* parcel, const ui::Transform& transform) {
+ return parcel->writeFloat(transform.dsdx()) ?:
+ parcel->writeFloat(transform.dtdx()) ?:
+ parcel->writeFloat(transform.tx()) ?:
+ parcel->writeFloat(transform.dtdy()) ?:
+ parcel->writeFloat(transform.dsdy()) ?:
+ parcel->writeFloat(transform.ty());
+}
+
+status_t readTransform(const android::Parcel* parcel, ui::Transform& transform) {
+ float dsdx, dtdx, tx, dtdy, dsdy, ty;
+
+ const status_t status = parcel->readFloat(&dsdx) ?:
+ parcel->readFloat(&dtdx) ?:
+ parcel->readFloat(&tx) ?:
+ parcel->readFloat(&dtdy) ?:
+ parcel->readFloat(&dsdy) ?:
+ parcel->readFloat(&ty);
+ if (status != OK) {
+ return status;
+ }
+
+ transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
+ return OK;
+}
+
} // namespace
void WindowInfo::setInputConfig(ftl::Flags<InputConfig> config, bool value) {
@@ -73,10 +99,6 @@ void WindowInfo::addTouchableRegion(const Rect& region) {
touchableRegion.orSelf(region);
}
-bool WindowInfo::supportsSplitTouch() const {
- return !inputConfig.test(InputConfig::PREVENT_SPLITTING);
-}
-
bool WindowInfo::isSpy() const {
return inputConfig.test(InputConfig::SPY);
}
@@ -135,12 +157,7 @@ status_t WindowInfo::writeToParcel(android::Parcel* parcel) const {
parcel->writeInt32(surfaceInset) ?:
parcel->writeFloat(globalScaleFactor) ?:
parcel->writeFloat(alpha) ?:
- parcel->writeFloat(transform.dsdx()) ?:
- parcel->writeFloat(transform.dtdx()) ?:
- parcel->writeFloat(transform.tx()) ?:
- parcel->writeFloat(transform.dtdy()) ?:
- parcel->writeFloat(transform.dsdy()) ?:
- parcel->writeFloat(transform.ty()) ?:
+ writeTransform(parcel, transform) ?:
parcel->writeInt32(static_cast<int32_t>(touchOcclusionMode)) ?:
parcel->writeInt32(ownerPid.val()) ?:
parcel->writeInt32(ownerUid.val()) ?:
@@ -153,8 +170,12 @@ status_t WindowInfo::writeToParcel(android::Parcel* parcel) const {
parcel->writeStrongBinder(touchableRegionCropHandle.promote()) ?:
parcel->writeStrongBinder(windowToken) ?:
parcel->writeStrongBinder(focusTransferTarget) ?:
- parcel->writeBool(canOccludePresentation);
+ parcel->writeBool(canOccludePresentation) ?:
+ parcel->writeBool(cloneLayerStackTransform.has_value());
// clang-format on
+ if (cloneLayerStackTransform) {
+ status = status ?: writeTransform(parcel, *cloneLayerStackTransform);
+ }
return status;
}
@@ -174,10 +195,10 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) {
return status;
}
- float dsdx, dtdx, tx, dtdy, dsdy, ty;
int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt, ownerPidInt, ownerUidInt,
displayIdInt;
sp<IBinder> touchableRegionCropHandleSp;
+ bool hasCloneLayerStackTransform = false;
// clang-format off
status = parcel->readInt32(&lpFlags) ?:
@@ -188,12 +209,7 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) {
parcel->readInt32(&surfaceInset) ?:
parcel->readFloat(&globalScaleFactor) ?:
parcel->readFloat(&alpha) ?:
- parcel->readFloat(&dsdx) ?:
- parcel->readFloat(&dtdx) ?:
- parcel->readFloat(&tx) ?:
- parcel->readFloat(&dtdy) ?:
- parcel->readFloat(&dsdy) ?:
- parcel->readFloat(&ty) ?:
+ readTransform(parcel, /*byRef*/ transform) ?:
parcel->readInt32(&touchOcclusionModeInt) ?:
parcel->readInt32(&ownerPidInt) ?:
parcel->readInt32(&ownerUidInt) ?:
@@ -206,8 +222,8 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) {
parcel->readNullableStrongBinder(&touchableRegionCropHandleSp) ?:
parcel->readNullableStrongBinder(&windowToken) ?:
parcel->readNullableStrongBinder(&focusTransferTarget) ?:
- parcel->readBool(&canOccludePresentation);
-
+ parcel->readBool(&canOccludePresentation)?:
+ parcel->readBool(&hasCloneLayerStackTransform);
// clang-format on
if (status != OK) {
@@ -216,7 +232,6 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) {
layoutParamsFlags = ftl::Flags<Flag>(lpFlags);
layoutParamsType = static_cast<Type>(lpType);
- transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
touchOcclusionMode = static_cast<TouchOcclusionMode>(touchOcclusionModeInt);
inputConfig = ftl::Flags<InputConfig>(inputConfigInt);
ownerPid = Pid{ownerPidInt};
@@ -224,6 +239,15 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) {
touchableRegionCropHandle = touchableRegionCropHandleSp;
displayId = ui::LogicalDisplayId{displayIdInt};
+ cloneLayerStackTransform =
+ hasCloneLayerStackTransform ? std::make_optional<ui::Transform>() : std::nullopt;
+ if (cloneLayerStackTransform) {
+ status = readTransform(parcel, /*byRef*/ *cloneLayerStackTransform);
+ if (status != OK) {
+ return status;
+ }
+ }
+
return OK;
}
diff --git a/libs/gui/aidl/android/gui/ActivePicture.aidl b/libs/gui/aidl/android/gui/ActivePicture.aidl
new file mode 100644
index 0000000000..9b83be16ca
--- /dev/null
+++ b/libs/gui/aidl/android/gui/ActivePicture.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 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 android.gui;
+
+/**
+ * Visible content that is using picture processing.
+ * @hide
+ */
+parcelable ActivePicture {
+ /** The layer ID that is using picture processing. */
+ int layerId;
+
+ /** UID that owns layer using picture processing. */
+ int ownerUid;
+
+ /** ID of the picture profile that was used to configure the picture processing. */
+ long pictureProfileId;
+}
diff --git a/libs/gui/aidl/android/gui/CaptureArgs.aidl b/libs/gui/aidl/android/gui/CaptureArgs.aidl
index 4920344e0e..2bbed2b9d6 100644
--- a/libs/gui/aidl/android/gui/CaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl
@@ -69,10 +69,5 @@ parcelable CaptureArgs {
// exact colorspace is not an appropriate intermediate result.
// Note that if the caller is requesting a specific dataspace, this hint does nothing.
boolean hintForSeamlessTransition = false;
-
- // Allows the screenshot to attach a gainmap, which allows for a per-pixel
- // transformation of the screenshot to another luminance range, typically
- // mapping an SDR base image into HDR.
- boolean attachGainmap = false;
}
diff --git a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl
index 3114929e86..26c12c56f3 100644
--- a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl
+++ b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl
@@ -17,6 +17,7 @@
package android.gui;
import android.gui.DisplayMode;
+import android.gui.FrameRateCategoryRate;
import android.gui.HdrCapabilities;
// Information about a physical display which may change on hotplug reconnect.
@@ -43,4 +44,13 @@ parcelable DynamicDisplayInfo {
// The boot display mode preferred by the implementation.
int preferredBootDisplayMode;
+
+ // Represents whether display supports ARR.
+ boolean hasArrSupport;
+
+ // Represents frame rate for FrameRateCategory Normal and High.
+ FrameRateCategoryRate frameRateCategoryRate;
+
+ // All the refresh rates supported for the default display mode.
+ float[] supportedRefreshRates;
}
diff --git a/libs/gui/aidl/android/gui/FrameRateCategoryRate.aidl b/libs/gui/aidl/android/gui/FrameRateCategoryRate.aidl
new file mode 100644
index 0000000000..f30280139f
--- /dev/null
+++ b/libs/gui/aidl/android/gui/FrameRateCategoryRate.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 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 android.gui;
+
+/** @hide */
+// Represents frame rate for FrameRateCategory Normal and High.
+parcelable FrameRateCategoryRate {
+ float normal;
+ float high;
+} \ No newline at end of file
diff --git a/libs/gui/aidl/android/gui/IActivePictureListener.aidl b/libs/gui/aidl/android/gui/IActivePictureListener.aidl
new file mode 100644
index 0000000000..ad310bbd66
--- /dev/null
+++ b/libs/gui/aidl/android/gui/IActivePictureListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 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 android.gui;
+
+import android.gui.ActivePicture;
+
+/**
+ * Receive callbacks whenever the visible content using picture profiles changes.
+ * @hide
+ */
+interface IActivePictureListener {
+ /**
+ * Callback reporting the visible content on the screen using picture profiles.
+ */
+ oneway void onActivePicturesChanged(in ActivePicture[] activePictures);
+}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index ac14138e8c..da47ee27ba 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -33,6 +33,7 @@ import android.gui.FrameEvent;
import android.gui.FrameStats;
import android.gui.HdrConversionCapability;
import android.gui.HdrConversionStrategy;
+import android.gui.IActivePictureListener;
import android.gui.IDisplayEventConnection;
import android.gui.IFpsListener;
import android.gui.IHdrLayerInfoListener;
@@ -228,6 +229,11 @@ interface ISurfaceComposer {
void setGameContentType(IBinder display, boolean on);
/**
+ * Gets the maximum number of picture profiles supported by the display.
+ */
+ int getMaxLayerPictureProfiles(IBinder display);
+
+ /**
* Capture the specified screen. This requires READ_FRAME_BUFFER
* permission. This function will fail if there is a secure window on
* screen and DisplayCaptureArgs.captureSecureLayers is false.
@@ -599,4 +605,16 @@ interface ISurfaceComposer {
* past the provided VSync.
*/
oneway void removeJankListener(int layerId, IJankListener listener, long afterVsync);
+
+ /**
+ * Adds a listener used to monitor visible content that is being processed with picture
+ * profiles.
+ */
+ oneway void addActivePictureListener(IActivePictureListener listener);
+
+ /**
+ * Removes a listener used to monitor visible content that is being processed with picture
+ * profiles.
+ */
+ oneway void removeActivePictureListener(IActivePictureListener listener);
}
diff --git a/libs/gui/aidl/android/gui/LutProperties.aidl b/libs/gui/aidl/android/gui/LutProperties.aidl
new file mode 100644
index 0000000000..84c7013cda
--- /dev/null
+++ b/libs/gui/aidl/android/gui/LutProperties.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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 android.gui;
+
+/**
+ * This mirrors aidl::android::hardware::graphics::composer3::LutProperties definition.
+ * @hide
+ */
+parcelable LutProperties {
+ @Backing(type="int")
+ enum Dimension { ONE_D = 1, THREE_D = 3 }
+ Dimension dimension;
+
+ int size;
+ @Backing(type="int")
+ enum SamplingKey { RGB, MAX_RGB, CIE_Y }
+ SamplingKey[] samplingKeys;
+} \ No newline at end of file
diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl
index 5fb1a83c65..7f41bdab2f 100644
--- a/libs/gui/aidl/android/gui/OverlayProperties.aidl
+++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl
@@ -16,6 +16,8 @@
package android.gui;
+import android.gui.LutProperties;
+
/** @hide */
parcelable OverlayProperties {
parcelable SupportedBufferCombinations {
@@ -27,4 +29,6 @@ parcelable OverlayProperties {
SupportedBufferCombinations[] combinations;
boolean supportMixedColorSpaces;
+
+ @nullable LutProperties[] lutProperties;
}
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 8592cffd15..db1b9fb8eb 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -17,7 +17,10 @@
#ifndef ANDROID_GUI_BLAST_BUFFER_QUEUE_H
#define ANDROID_GUI_BLAST_BUFFER_QUEUE_H
-#include <com_android_graphics_libgui_flags.h>
+#include <optional>
+#include <queue>
+
+#include <ftl/small_map.h>
#include <gui/BufferItem.h>
#include <gui/BufferItemConsumer.h>
#include <gui/IGraphicBufferConsumer.h>
@@ -29,32 +32,20 @@
#include <utils/RefBase.h>
#include <system/window.h>
-#include <queue>
#include <com_android_graphics_libgui_flags.h>
namespace android {
+// Sizes determined empirically to avoid allocations during common activity.
+constexpr size_t kSubmittedBuffersMapSizeHint = 8;
+constexpr size_t kDequeueTimestampsMapSizeHint = 32;
+
class BLASTBufferQueue;
class BufferItemConsumer;
class BLASTBufferItemConsumer : public BufferItemConsumer {
public:
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
- BLASTBufferItemConsumer(const sp<IGraphicBufferProducer>& producer,
- const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage,
- int bufferCount, bool controlledByApp, wp<BLASTBufferQueue> bbq)
- : BufferItemConsumer(producer, consumer, consumerUsage, bufferCount, controlledByApp),
-#else
- BLASTBufferItemConsumer(const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage,
- int bufferCount, bool controlledByApp, wp<BLASTBufferQueue> bbq)
- : BufferItemConsumer(consumer, consumerUsage, bufferCount, controlledByApp),
-#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
- mBLASTBufferQueue(std::move(bbq)),
- mCurrentlyConnected(false),
- mPreviouslyConnected(false) {
- }
-
void onDisconnect() override EXCLUDES(mMutex);
void addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
FrameEventHistoryDelta* outDelta) override EXCLUDES(mMutex);
@@ -75,6 +66,23 @@ protected:
#endif
private:
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ BLASTBufferItemConsumer(const sp<IGraphicBufferProducer>& producer,
+ const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage,
+ int bufferCount, bool controlledByApp, wp<BLASTBufferQueue> bbq)
+ : BufferItemConsumer(producer, consumer, consumerUsage, bufferCount, controlledByApp),
+#else
+ BLASTBufferItemConsumer(const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage,
+ int bufferCount, bool controlledByApp, wp<BLASTBufferQueue> bbq)
+ : BufferItemConsumer(consumer, consumerUsage, bufferCount, controlledByApp),
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ mBLASTBufferQueue(std::move(bbq)),
+ mCurrentlyConnected(false),
+ mPreviouslyConnected(false) {
+ }
+
+ friend class sp<BLASTBufferItemConsumer>;
+
const wp<BLASTBufferQueue> mBLASTBufferQueue;
uint64_t mCurrentFrameNumber GUARDED_BY(mMutex) = 0;
@@ -88,10 +96,6 @@ private:
class BLASTBufferQueue : public ConsumerBase::FrameAvailableListener {
public:
- BLASTBufferQueue(const std::string& name, bool updateDestinationFrame = true);
- BLASTBufferQueue(const std::string& name, const sp<SurfaceControl>& surface, int width,
- int height, int32_t format);
-
sp<IGraphicBufferProducer> getIGraphicBufferProducer() const {
return mProducer;
}
@@ -143,13 +147,27 @@ public:
*/
void setTransactionHangCallback(std::function<void(const std::string&)> callback);
void setApplyToken(sp<IBinder>);
+
+ void setWaitForBufferReleaseCallback(std::function<void(const nsecs_t)> callback)
+ EXCLUDES(mWaitForBufferReleaseMutex);
+ std::function<void(const nsecs_t)> getWaitForBufferReleaseCallback() const
+ EXCLUDES(mWaitForBufferReleaseMutex);
+
virtual ~BLASTBufferQueue();
void onFirstRef() override;
private:
+ // Not public to ensure construction via sp<>::make().
+ BLASTBufferQueue(const std::string& name, bool updateDestinationFrame = true);
+
+ friend class sp<BLASTBufferQueue>;
friend class BLASTBufferQueueHelper;
friend class BBQBufferQueueProducer;
+ friend class TestBLASTBufferQueue;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ friend class BBQBufferQueueCore;
+#endif
// can't be copied
BLASTBufferQueue& operator = (const BLASTBufferQueue& rhs);
@@ -182,6 +200,7 @@ private:
sp<SurfaceControl> mSurfaceControl GUARDED_BY(mMutex);
mutable std::mutex mMutex;
+ mutable std::mutex mWaitForBufferReleaseMutex;
std::condition_variable mCallbackCV;
// BufferQueue internally allows 1 more than
@@ -197,7 +216,7 @@ private:
// Keep a reference to the submitted buffers so we can release when surfaceflinger drops the
// buffer or the buffer has been presented and a new buffer is ready to be presented.
- std::unordered_map<ReleaseCallbackId, BufferItem, ReleaseBufferCallbackIdHash> mSubmitted
+ ftl::SmallMap<ReleaseCallbackId, BufferItem, kSubmittedBuffersMapSizeHint> mSubmitted
GUARDED_BY(mMutex);
// Keep a queue of the released buffers instead of immediately releasing
@@ -219,6 +238,10 @@ private:
ui::Size mRequestedSize GUARDED_BY(mMutex);
int32_t mFormat GUARDED_BY(mMutex);
+ // Keep a copy of the current picture profile handle, so it can be moved to a new
+ // SurfaceControl when BBQ migrates via ::update.
+ std::optional<PictureProfileHandle> mPictureProfileHandle;
+
struct BufferInfo {
bool hasBuffer = false;
uint32_t width;
@@ -278,8 +301,8 @@ private:
std::mutex mTimestampMutex;
// Tracks buffer dequeue times by the client. This info is sent to SurfaceFlinger which uses
// it for debugging purposes.
- std::unordered_map<uint64_t /* bufferId */, nsecs_t> mDequeueTimestamps
- GUARDED_BY(mTimestampMutex);
+ ftl::SmallMap<uint64_t /* bufferId */, nsecs_t, kDequeueTimestampsMapSizeHint>
+ mDequeueTimestamps GUARDED_BY(mTimestampMutex);
// Keep track of SurfaceControls that have submitted a transaction and BBQ is waiting on a
// callback for them.
@@ -316,49 +339,50 @@ private:
std::unordered_set<uint64_t> mSyncedFrameNumbers GUARDED_BY(mMutex);
+ std::function<void(const nsecs_t)> mWaitForBufferReleaseCallback
+ GUARDED_BY(mWaitForBufferReleaseMutex);
#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ // BufferReleaseChannel is used to communicate buffer releases from SurfaceFlinger to the
+ // client.
+ std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> mBufferReleaseConsumer;
+ std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> mBufferReleaseProducer;
+
+ void updateBufferReleaseProducer() REQUIRES(mMutex);
+ void drainBufferReleaseConsumer();
+
+ // BufferReleaseReader is used to do blocking but interruptible reads from the buffer
+ // release channel. To implement this, BufferReleaseReader owns an epoll file descriptor that
+ // is configured to wake up when either the BufferReleaseReader::ConsumerEndpoint or an eventfd
+ // becomes readable. Interrupts are necessary because a free buffer may become available for
+ // reasons other than a buffer release from the producer.
class BufferReleaseReader {
public:
- BufferReleaseReader() = default;
- BufferReleaseReader(std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint>);
- BufferReleaseReader& operator=(BufferReleaseReader&&);
+ explicit BufferReleaseReader(BLASTBufferQueue&);
+
+ BufferReleaseReader(const BufferReleaseReader&) = delete;
+ BufferReleaseReader& operator=(const BufferReleaseReader&) = delete;
// Block until we can read a buffer release message.
//
// Returns:
// * OK if a ReleaseCallbackId and Fence were successfully read.
// * WOULD_BLOCK if the blocking read was interrupted by interruptBlockingRead.
+ // * TIMED_OUT if the blocking read timed out.
// * UNKNOWN_ERROR if something went wrong.
status_t readBlocking(ReleaseCallbackId& outId, sp<Fence>& outReleaseFence,
- uint32_t& outMaxAcquiredBufferCount);
+ uint32_t& outMaxAcquiredBufferCount, nsecs_t timeout);
- // Signals the reader's eventfd to wake up any threads waiting on readBlocking.
void interruptBlockingRead();
+ void clearInterrupts();
private:
- std::mutex mMutex;
- std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> mEndpoint GUARDED_BY(mMutex);
+ BLASTBufferQueue& mBbq;
+
android::base::unique_fd mEpollFd;
android::base::unique_fd mEventFd;
};
- // BufferReleaseChannel is used to communicate buffer releases from SurfaceFlinger to
- // the client. See BBQBufferQueueProducer::dequeueBuffer for details.
- std::shared_ptr<BufferReleaseReader> mBufferReleaseReader;
- std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> mBufferReleaseProducer;
-
- class BufferReleaseThread {
- public:
- BufferReleaseThread() = default;
- ~BufferReleaseThread();
- void start(const sp<BLASTBufferQueue>&);
-
- private:
- std::shared_ptr<std::atomic_bool> mRunning;
- std::shared_ptr<BufferReleaseReader> mReader;
- };
-
- BufferReleaseThread mBufferReleaseThread;
+ std::optional<BufferReleaseReader> mBufferReleaseReader;
#endif
};
diff --git a/libs/gui/include/gui/BufferItem.h b/libs/gui/include/gui/BufferItem.h
index 218bb424fb..2f85c62a54 100644
--- a/libs/gui/include/gui/BufferItem.h
+++ b/libs/gui/include/gui/BufferItem.h
@@ -17,9 +17,12 @@
#ifndef ANDROID_GUI_BUFFERITEM_H
#define ANDROID_GUI_BUFFERITEM_H
+#include <optional>
+
#include <gui/HdrMetadata.h>
#include <ui/FenceTime.h>
+#include <ui/PictureProfileHandle.h>
#include <ui/Rect.h>
#include <ui/Region.h>
@@ -91,6 +94,10 @@ class BufferItem : public Flattenable<BufferItem> {
// mHdrMetadata is the HDR metadata associated with this buffer slot.
HdrMetadata mHdrMetadata;
+ // mPictureProfileHandle is a handle that points to a set of parameters that configure picture
+ // processing hardware to enhance the quality of buffer contents.
+ std::optional<PictureProfileHandle> mPictureProfileHandle;
+
// mFrameNumber is the number of the queued frame for this slot.
uint64_t mFrameNumber;
diff --git a/libs/gui/include/gui/BufferItemConsumer.h b/libs/gui/include/gui/BufferItemConsumer.h
index 6810edaf7c..0bfa7b222e 100644
--- a/libs/gui/include/gui/BufferItemConsumer.h
+++ b/libs/gui/include/gui/BufferItemConsumer.h
@@ -47,6 +47,16 @@ class BufferItemConsumer: public ConsumerBase
enum { INVALID_BUFFER_SLOT = BufferQueue::INVALID_BUFFER_SLOT };
enum { NO_BUFFER_AVAILABLE = BufferQueue::NO_BUFFER_AVAILABLE };
+ static std::tuple<sp<BufferItemConsumer>, sp<Surface>> create(
+ uint64_t consumerUsage, int bufferCount = DEFAULT_MAX_BUFFERS,
+ bool controlledByApp = false, bool isConsumerSurfaceFlinger = false);
+
+ static sp<BufferItemConsumer> create(const sp<IGraphicBufferConsumer>& consumer,
+ uint64_t consumerUsage,
+ int bufferCount = DEFAULT_MAX_BUFFERS,
+ bool controlledByApp = false)
+ __attribute((deprecated("Prefer ctors that create their own surface and consumer.")));
+
// Create a new buffer item consumer. The consumerUsage parameter determines
// the consumer usage flags passed to the graphics allocator. The
// bufferCount parameter specifies how many buffers can be locked for user
diff --git a/libs/gui/include/gui/BufferQueue.h b/libs/gui/include/gui/BufferQueue.h
index 0948c4d076..7b97e13649 100644
--- a/libs/gui/include/gui/BufferQueue.h
+++ b/libs/gui/include/gui/BufferQueue.h
@@ -57,7 +57,7 @@ public:
// reference in the BufferQueue class is because we're planning to expose the
// consumer side of a BufferQueue as a binder interface, which doesn't support
// weak references.
- class ProxyConsumerListener : public BnConsumerListener {
+ class ProxyConsumerListener : public IConsumerListener {
public:
explicit ProxyConsumerListener(const wp<ConsumerListener>& consumerListener);
~ProxyConsumerListener() override;
@@ -76,6 +76,9 @@ public:
void onSetFrameRate(float frameRate, int8_t compatibility,
int8_t changeFrameRateStrategy) override;
#endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ void onSlotCountChanged(int slotCount) override;
+#endif
private:
// mConsumerListener is a weak reference to the IConsumerListener. This is
// the raison d'etre of ProxyConsumerListener.
diff --git a/libs/gui/include/gui/BufferQueueConsumer.h b/libs/gui/include/gui/BufferQueueConsumer.h
index 6aa801ab86..ab1231ad6b 100644
--- a/libs/gui/include/gui/BufferQueueConsumer.h
+++ b/libs/gui/include/gui/BufferQueueConsumer.h
@@ -28,8 +28,7 @@ namespace android {
class BufferQueueCore;
-class BufferQueueConsumer : public BnGraphicBufferConsumer {
-
+class BufferQueueConsumer : public IGraphicBufferConsumer {
public:
explicit BufferQueueConsumer(const sp<BufferQueueCore>& core);
~BufferQueueConsumer() override;
@@ -65,13 +64,14 @@ public:
// any references to the just-released buffer that it might have, as if it
// had received a onBuffersReleased() call with a mask set for the released
// buffer.
- //
- // Note that the dependencies on EGL will be removed once we switch to using
- // the Android HW Sync HAL.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ virtual status_t releaseBuffer(int slot, uint64_t frameNumber,
+ const sp<Fence>& releaseFence) override;
+#else
virtual status_t releaseBuffer(int slot, uint64_t frameNumber,
const sp<Fence>& releaseFence, EGLDisplay display,
EGLSyncKHR fence);
-
+#endif
// connect connects a consumer to the BufferQueue. Only one
// consumer may be connected, and when that consumer disconnects the
// BufferQueue is placed into the "abandoned" state, causing most
@@ -96,11 +96,26 @@ public:
// This should be called from the onBuffersReleased() callback.
virtual status_t getReleasedBuffers(uint64_t* outSlotMask);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ // getReleasedBuffers sets the values pointed to by outSlotMask to the bits
+ // indicating which buffer slots have been released by the BufferQueue
+ // but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback when
+ // allowUnlimitedSlots has been called.
+ virtual status_t getReleasedBuffersExtended(std::vector<bool>* outSlotMask) override;
+#endif
+
// setDefaultBufferSize is used to set the size of buffers returned by
// dequeueBuffer when a width and height of zero is requested. Default
// is 1x1.
virtual status_t setDefaultBufferSize(uint32_t width, uint32_t height);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ // see IGraphicBufferConsumer::allowUnlimitedSlots
+ virtual status_t allowUnlimitedSlots(bool allowUnlimitedSlots) override;
+#endif
+
// see IGraphicBufferConsumer::setMaxBufferCount
virtual status_t setMaxBufferCount(int bufferCount);
@@ -152,6 +167,7 @@ public:
// dump our state in a String
status_t dumpState(const String8& prefix, String8* outResult) const override;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
// Functions required for backwards compatibility.
// These will be modified/renamed in IGraphicBufferConsumer and will be
// removed from this class at that time. See b/13306289.
@@ -161,6 +177,7 @@ public:
const sp<Fence>& releaseFence) {
return releaseBuffer(buf, frameNumber, releaseFence, display, fence);
}
+#endif
virtual status_t consumerConnect(const sp<IConsumerListener>& consumer,
bool controlledByApp) {
diff --git a/libs/gui/include/gui/BufferQueueCore.h b/libs/gui/include/gui/BufferQueueCore.h
index d5dd7c897c..7f92a46053 100644
--- a/libs/gui/include/gui/BufferQueueCore.h
+++ b/libs/gui/include/gui/BufferQueueCore.h
@@ -32,10 +32,11 @@
#include <utils/Trace.h>
#include <utils/Vector.h>
+#include <condition_variable>
#include <list>
-#include <set>
#include <mutex>
-#include <condition_variable>
+#include <set>
+#include <vector>
#define ATRACE_BUFFER_INDEX(index) \
do { \
@@ -80,10 +81,21 @@ public:
BufferQueueCore();
virtual ~BufferQueueCore();
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+protected:
+ // Wake up any threads waiting for a buffer release. The BufferQueue mutex should always held
+ // when this method is called.
+ virtual void notifyBufferReleased() const;
+#endif
+
private:
// Dump our state in a string
void dumpState(const String8& prefix, String8* outResult) const;
+ // getTotalSlotCountLocked returns the total number of slots in use by the
+ // buffer queue at this time.
+ int getTotalSlotCountLocked() const;
+
// getMinUndequeuedBufferCountLocked returns the minimum number of buffers
// that must remain in a state other than DEQUEUED. The async parameter
// tells whether we're in asynchronous mode.
@@ -113,6 +125,10 @@ private:
int getMaxBufferCountLocked(bool asyncMode,
bool dequeueBufferCannotBlock, int maxBufferCount) const;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ // This resizes mSlots to the given size, but only if it's increasing.
+ status_t extendSlotCountLocked(int size);
+#endif
// clearBufferSlotLocked frees the GraphicBuffer and sync resources for the
// given slot.
void clearBufferSlotLocked(int slot);
@@ -197,7 +213,7 @@ private:
// mConnectedProducerListener will not trigger onBufferAttached() callback.
bool mBufferAttachedCbEnabled;
- // mSlots is an array of buffer slots that must be mirrored on the producer
+ // mSlots is a collection of buffer slots that must be mirrored on the producer
// side. This allows buffer ownership to be transferred between the producer
// and consumer without sending a GraphicBuffer over Binder. The entire
// array is initialized to NULL at construction time, and buffers are
@@ -259,8 +275,14 @@ private:
// is specified.
android_dataspace mDefaultBufferDataSpace;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ // mAllowExtendedSlotCount is set by the consumer to permit the producer to
+ // request an unlimited number of slots.
+ bool mAllowExtendedSlotCount;
+#endif
+
// mMaxBufferCount is the limit on the number of buffers that will be
- // allocated at one time. This limit can be set by the consumer.
+ // allocated at one time.
int mMaxBufferCount;
// mMaxAcquiredBufferCount is the number of buffers that the consumer may
diff --git a/libs/gui/include/gui/BufferQueueDefs.h b/libs/gui/include/gui/BufferQueueDefs.h
index ffafb49615..42cf439450 100644
--- a/libs/gui/include/gui/BufferQueueDefs.h
+++ b/libs/gui/include/gui/BufferQueueDefs.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_GUI_BUFFERQUEUECOREDEFS_H
#define ANDROID_GUI_BUFFERQUEUECOREDEFS_H
+#include <com_android_graphics_libgui_flags.h>
#include <gui/BufferSlot.h>
#include <ui/BufferQueueDefs.h>
@@ -24,7 +25,11 @@ namespace android {
class BufferQueueCore;
namespace BufferQueueDefs {
- typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ typedef std::vector<BufferSlot> SlotsType;
+#else
+ typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
+#endif
} // namespace BufferQueueDefs
} // namespace android
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index 37a960708c..50abadb922 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -47,6 +47,11 @@ public:
// flags indicating that previously-returned buffers are no longer valid.
virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ // see IGraphicsBufferProducer::extendSlotCount
+ virtual status_t extendSlotCount(int size) override;
+#endif
+
// see IGraphicsBufferProducer::setMaxDequeuedBufferCount
virtual status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers);
@@ -218,6 +223,14 @@ protected:
// total maximum buffer count for the buffer queue (dequeued AND acquired)
status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers, int* maxBufferCount);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+ // Wait until a buffer has been released. The method may spuriously return OK when no buffer has
+ // been released. The BufferQueue mutex is passed in the locked state. It must be unlocked
+ // before waiting for a release and locked before returning.
+ virtual status_t waitForBufferRelease(std::unique_lock<std::mutex>& lock,
+ nsecs_t timeout) const;
+#endif
+
private:
// This is required by the IBinder::DeathRecipient interface
virtual void binderDied(const wp<IBinder>& who);
diff --git a/libs/gui/include/gui/BufferReleaseChannel.h b/libs/gui/include/gui/BufferReleaseChannel.h
index 51fe0b6fab..0edadecedf 100644
--- a/libs/gui/include/gui/BufferReleaseChannel.h
+++ b/libs/gui/include/gui/BufferReleaseChannel.h
@@ -69,7 +69,8 @@ public:
sp<Fence>& outReleaseFence, uint32_t& maxAcquiredBufferCount);
private:
- std::vector<uint8_t> mFlattenedBuffer;
+ std::mutex mMutex;
+ std::vector<uint8_t> mFlattenedBuffer GUARDED_BY(mMutex);
};
class ProducerEndpoint : public Endpoint, public Parcelable {
diff --git a/libs/gui/include/gui/BufferSlot.h b/libs/gui/include/gui/BufferSlot.h
index 5b32710135..e83d2e33f2 100644
--- a/libs/gui/include/gui/BufferSlot.h
+++ b/libs/gui/include/gui/BufferSlot.h
@@ -174,26 +174,30 @@ struct BufferState {
};
struct BufferSlot {
-
BufferSlot()
- : mGraphicBuffer(nullptr),
- mEglDisplay(EGL_NO_DISPLAY),
- mBufferState(),
- mRequestBufferCalled(false),
- mFrameNumber(0),
- mEglFence(EGL_NO_SYNC_KHR),
- mFence(Fence::NO_FENCE),
- mAcquireCalled(false),
- mNeedsReallocation(false) {
+ : mGraphicBuffer(nullptr),
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ mEglDisplay(EGL_NO_DISPLAY),
+#endif
+ mBufferState(),
+ mRequestBufferCalled(false),
+ mFrameNumber(0),
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ mEglFence(EGL_NO_SYNC_KHR),
+#endif
+ mFence(Fence::NO_FENCE),
+ mAcquireCalled(false),
+ mNeedsReallocation(false) {
}
// mGraphicBuffer points to the buffer allocated for this slot or is NULL
// if no buffer has been allocated.
sp<GraphicBuffer> mGraphicBuffer;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
// mEglDisplay is the EGLDisplay used to create EGLSyncKHR objects.
EGLDisplay mEglDisplay;
-
+#endif
// mBufferState is the current state of this buffer slot.
BufferState mBufferState;
@@ -207,12 +211,14 @@ struct BufferSlot {
// may be released before their release fence is signaled).
uint64_t mFrameNumber;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
// mEglFence is the EGL sync object that must signal before the buffer
// associated with this buffer slot may be dequeued. It is initialized
// to EGL_NO_SYNC_KHR when the buffer is created and may be set to a
// new sync object in releaseBuffer. (This is deprecated in favor of
// mFence, below.)
EGLSyncKHR mEglFence;
+#endif
// mFence is a fence which will signal when work initiated by the
// previous owner of the buffer is finished. When the buffer is FREE,
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 2e5aa4a893..5862967d4a 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -103,7 +103,7 @@ public:
virtual void handleMessage(const Message& message) override;
static void initJVM(JNIEnv* env);
- static Choreographer* getForThread();
+ static sp<Choreographer> getForThread();
static void signalRefreshRateCallbacks(nsecs_t vsyncPeriod) EXCLUDES(gChoreographers.lock);
static int64_t getStartTimeNanosForVsyncId(AVsyncId vsyncId) EXCLUDES(gChoreographers.lock);
virtual ~Choreographer() override EXCLUDES(gChoreographers.lock);
@@ -127,6 +127,7 @@ private:
std::vector<FrameRateOverride> overrides) override;
void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
int32_t maxLevel) override;
+ void dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) override;
void scheduleCallbacks();
diff --git a/libs/gui/include/gui/ConsumerBase.h b/libs/gui/include/gui/ConsumerBase.h
index e976aa48be..477d98df10 100644
--- a/libs/gui/include/gui/ConsumerBase.h
+++ b/libs/gui/include/gui/ConsumerBase.h
@@ -98,6 +98,8 @@ public:
status_t detachBuffer(const sp<GraphicBuffer>& buffer);
#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+ status_t addReleaseFence(const sp<GraphicBuffer> buffer, const sp<Fence>& fence);
+
// See IGraphicBufferConsumer::setDefaultBufferSize
status_t setDefaultBufferSize(uint32_t width, uint32_t height);
@@ -121,9 +123,7 @@ public:
// See IGraphicBufferConsumer::setMaxAcquiredBufferCount
status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers);
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
status_t setConsumerIsProtected(bool isProtected);
-#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
// See IGraphicBufferConsumer::getSidebandStream
sp<NativeHandle> getSidebandStream() const;
@@ -185,7 +185,9 @@ protected:
virtual void onFrameDetached(const uint64_t bufferId) override;
virtual void onBuffersReleased() override;
virtual void onSidebandStreamChanged() override;
-
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ virtual void onSlotCountChanged(int slotCount) override;
+#endif
virtual int getSlotForBufferLocked(const sp<GraphicBuffer>& buffer);
virtual status_t detachBufferLocked(int slotIndex);
@@ -243,10 +245,13 @@ protected:
// must take place when a buffer is released back to the BufferQueue. If
// it is overridden the derived class's implementation must call
// ConsumerBase::releaseBufferLocked.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer);
+#else
virtual status_t releaseBufferLocked(int slot,
const sp<GraphicBuffer> graphicBuffer,
EGLDisplay display = EGL_NO_DISPLAY, EGLSyncKHR eglFence = EGL_NO_SYNC_KHR);
-
+#endif
// returns true iff the slot still has the graphicBuffer in it.
bool stillTracking(int slot, const sp<GraphicBuffer> graphicBuffer);
@@ -284,7 +289,11 @@ protected:
// slot that has not yet been used. The buffer allocated to a slot will also
// be replaced if the requested buffer usage or geometry differs from that
// of the buffer allocated to a slot.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ std::vector<Slot> mSlots;
+#else
Slot mSlots[BufferQueueDefs::NUM_BUFFER_SLOTS];
+#endif
// mAbandoned indicates that the BufferQueue will no longer be used to
// consume images buffers pushed to it using the IGraphicBufferProducer
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index 82cd50c7bd..b06ad077f4 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -68,6 +68,8 @@ private:
virtual void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
int32_t maxLevel) = 0;
+ virtual void dispatchModeRejected(PhysicalDisplayId displayId, int32_t modeId) = 0;
+
bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId,
uint32_t* outCount, VsyncEventData* outVsyncEventData);
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 4dbf9e1929..f51390a0d8 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -55,19 +55,20 @@ static inline constexpr uint32_t fourcc(char c1, char c2, char c3, char c4) {
static_cast<uint32_t>(c4);
}
+enum class DisplayEventType : uint32_t {
+ DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),
+ DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),
+ DISPLAY_EVENT_MODE_CHANGE = fourcc('m', 'o', 'd', 'e'),
+ DISPLAY_EVENT_MODE_REJECTION = fourcc('r', 'e', 'j', 'e'),
+ DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'),
+ DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'),
+ DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'),
+ DISPLAY_EVENT_HDCP_LEVELS_CHANGE = fourcc('h', 'd', 'c', 'p'),
+};
+
// ----------------------------------------------------------------------------
class DisplayEventReceiver {
public:
- enum {
- DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),
- DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),
- DISPLAY_EVENT_MODE_CHANGE = fourcc('m', 'o', 'd', 'e'),
- DISPLAY_EVENT_NULL = fourcc('n', 'u', 'l', 'l'),
- DISPLAY_EVENT_FRAME_RATE_OVERRIDE = fourcc('r', 'a', 't', 'e'),
- DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH = fourcc('f', 'l', 's', 'h'),
- DISPLAY_EVENT_HDCP_LEVELS_CHANGE = fourcc('h', 'd', 'c', 'p'),
- };
-
struct Event {
// We add __attribute__((aligned(8))) for nsecs_t fields because
// we need to make sure all fields are aligned the same with x86
@@ -76,7 +77,7 @@ public:
// https://en.wikipedia.org/wiki/Data_structure_alignment
struct Header {
- uint32_t type;
+ DisplayEventType type;
PhysicalDisplayId displayId __attribute__((aligned(8)));
nsecs_t timestamp __attribute__((aligned(8)));
};
@@ -96,6 +97,10 @@ public:
nsecs_t vsyncPeriod __attribute__((aligned(8)));
};
+ struct ModeRejection {
+ int32_t modeId;
+ };
+
struct FrameRateOverride {
uid_t uid __attribute__((aligned(8)));
float frameRateHz __attribute__((aligned(8)));
@@ -117,9 +122,10 @@ public:
ModeChange modeChange;
FrameRateOverride frameRateOverride;
HdcpLevelsChange hdcpLevelsChange;
+ ModeRejection modeRejection;
};
};
- static_assert(sizeof(Event) == 216);
+ static_assert(sizeof(Event) == 224);
public:
/*
diff --git a/libs/gui/include/gui/DisplayLuts.h b/libs/gui/include/gui/DisplayLuts.h
new file mode 100644
index 0000000000..187381c4ae
--- /dev/null
+++ b/libs/gui/include/gui/DisplayLuts.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2024 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/unique_fd.h>
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <cutils/ashmem.h>
+#include <sys/mman.h>
+#include <algorithm>
+#include <ostream>
+#include <vector>
+
+namespace android::gui {
+
+struct DisplayLuts : public Parcelable {
+public:
+ struct Entry : public Parcelable {
+ Entry() {};
+ Entry(int32_t lutDimension, int32_t lutSize, int32_t lutSamplingKey)
+ : dimension(lutDimension), size(lutSize), samplingKey(lutSamplingKey) {}
+ int32_t dimension;
+ int32_t size;
+ int32_t samplingKey;
+
+ status_t writeToParcel(android::Parcel* parcel) const override;
+ status_t readFromParcel(const android::Parcel* parcel) override;
+ };
+
+ DisplayLuts() {}
+
+ DisplayLuts(base::unique_fd lutfd, std::vector<int32_t> lutoffsets,
+ std::vector<int32_t> lutdimensions, std::vector<int32_t> lutsizes,
+ std::vector<int32_t> lutsamplingKeys) {
+ fd = std::move(lutfd);
+ offsets = lutoffsets;
+ lutProperties.reserve(offsets.size());
+ for (size_t i = 0; i < lutoffsets.size(); i++) {
+ Entry entry{lutdimensions[i], lutsizes[i], lutsamplingKeys[i]};
+ lutProperties.emplace_back(entry);
+ }
+ }
+
+ status_t writeToParcel(android::Parcel* parcel) const override;
+ status_t readFromParcel(const android::Parcel* parcel) override;
+
+ const base::unique_fd& getLutFileDescriptor() const { return fd; }
+
+ std::vector<Entry> lutProperties;
+ std::vector<int32_t> offsets;
+
+private:
+ base::unique_fd fd;
+}; // struct DisplayLuts
+
+static inline void PrintTo(const std::vector<int32_t>& offsets, ::std::ostream* os) {
+ *os << "\n .offsets = {";
+ for (size_t i = 0; i < offsets.size(); i++) {
+ *os << offsets[i];
+ if (i != offsets.size() - 1) {
+ *os << ", ";
+ }
+ }
+ *os << "}";
+}
+
+static inline void PrintTo(const std::vector<DisplayLuts::Entry>& entries, ::std::ostream* os) {
+ *os << "\n .lutProperties = {\n";
+ for (auto& [dimension, size, samplingKey] : entries) {
+ *os << " Entry{"
+ << "dimension: " << dimension << ", size: " << size << ", samplingKey: " << samplingKey
+ << "}\n";
+ }
+ *os << " }";
+}
+
+static constexpr size_t kMaxPrintCount = 100;
+
+static inline void PrintTo(const std::vector<float>& buffer, size_t offset, int32_t dimension,
+ size_t size, ::std::ostream* os) {
+ size_t range = std::min(size, kMaxPrintCount);
+ *os << "{";
+ if (dimension == 1) {
+ for (size_t i = 0; i < range; i++) {
+ *os << buffer[offset + i];
+ if (i != range - 1) {
+ *os << ", ";
+ }
+ }
+ } else {
+ *os << "\n {R channel:";
+ for (size_t i = 0; i < range; i++) {
+ *os << buffer[offset + i];
+ if (i != range - 1) {
+ *os << ", ";
+ }
+ }
+ *os << "}\n {G channel:";
+ for (size_t i = 0; i < range; i++) {
+ *os << buffer[offset + size + i];
+ if (i != range - 1) {
+ *os << ", ";
+ }
+ }
+ *os << "}\n {B channel:";
+ for (size_t i = 0; i < range; i++) {
+ *os << buffer[offset + 2 * size + i];
+ if (i != range - 1) {
+ *os << ", ";
+ }
+ }
+ }
+ *os << "}";
+}
+
+static inline void PrintTo(const std::shared_ptr<DisplayLuts> luts, ::std::ostream* os) {
+ *os << "gui::DisplayLuts {";
+ auto& fd = luts->getLutFileDescriptor();
+ *os << "\n .pfd = " << fd.get();
+ if (fd.ok()) {
+ PrintTo(luts->offsets, os);
+ PrintTo(luts->lutProperties, os);
+ // decode luts
+ int32_t fullLength = luts->offsets[luts->offsets.size() - 1];
+ if (luts->lutProperties[luts->offsets.size() - 1].dimension == 1) {
+ fullLength += luts->lutProperties[luts->offsets.size() - 1].size;
+ } else {
+ fullLength += (luts->lutProperties[luts->offsets.size() - 1].size *
+ luts->lutProperties[luts->offsets.size() - 1].size *
+ luts->lutProperties[luts->offsets.size() - 1].size * 3);
+ }
+ size_t bufferSize = static_cast<size_t>(fullLength) * sizeof(float);
+ float* ptr = (float*)mmap(NULL, bufferSize, PROT_READ, MAP_SHARED, fd.get(), 0);
+ if (ptr == MAP_FAILED) {
+ *os << "\n .bufferdata cannot mmap!";
+ return;
+ }
+ std::vector<float> buffers(ptr, ptr + fullLength);
+ munmap(ptr, bufferSize);
+
+ *os << "\n .bufferdata = ";
+ for (size_t i = 0; i < luts->offsets.size(); i++) {
+ PrintTo(buffers, static_cast<size_t>(luts->offsets[i]),
+ luts->lutProperties[i].dimension,
+ static_cast<size_t>(luts->lutProperties[i].size), os);
+ }
+ }
+ *os << "\n }";
+}
+
+} // namespace android::gui \ No newline at end of file
diff --git a/libs/gui/include/gui/Flags.h b/libs/gui/include/gui/Flags.h
index 735375a1e7..446841bcd2 100644
--- a/libs/gui/include/gui/Flags.h
+++ b/libs/gui/include/gui/Flags.h
@@ -17,8 +17,41 @@
#pragma once
#include <com_android_graphics_libgui_flags.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+class IGraphicBufferProducer;
+class Surface;
+namespace view {
+class Surface;
+}
#define WB_CAMERA3_AND_PROCESSORS_WITH_DEPENDENCIES \
(COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CAMERA3_AND_PROCESSORS) && \
COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) && \
- COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)) \ No newline at end of file
+ COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS))
+
+#define WB_LIBCAMERASERVICE_WITH_DEPENDENCIES \
+ (WB_CAMERA3_AND_PROCESSORS_WITH_DEPENDENCIES && \
+ COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_LIBCAMERASERVICE))
+
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+typedef android::Surface SurfaceType;
+typedef android::view::Surface ParcelableSurfaceType;
+#else
+typedef android::IGraphicBufferProducer SurfaceType;
+typedef android::sp<android::IGraphicBufferProducer> ParcelableSurfaceType;
+#endif
+
+namespace flagtools {
+sp<SurfaceType> surfaceToSurfaceType(const sp<Surface>& surface);
+ParcelableSurfaceType surfaceToParcelableSurfaceType(const sp<Surface>& surface);
+ParcelableSurfaceType toParcelableSurfaceType(const view::Surface& surface);
+sp<IGraphicBufferProducer> surfaceTypeToIGBP(const sp<SurfaceType>& surface);
+bool isSurfaceTypeValid(const sp<SurfaceType>& surface);
+ParcelableSurfaceType convertSurfaceTypeToParcelable(sp<SurfaceType> surface);
+sp<SurfaceType> convertParcelableSurfaceTypeToSurface(const ParcelableSurfaceType& surface);
+} // namespace flagtools
+
+} // namespace android
diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h
index bfe3eb31e8..dbf707f35f 100644
--- a/libs/gui/include/gui/GLConsumer.h
+++ b/libs/gui/include/gui/GLConsumer.h
@@ -266,26 +266,28 @@ protected:
virtual status_t acquireBufferLocked(BufferItem *item, nsecs_t presentWhen,
uint64_t maxFrameNumber = 0) override;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ virtual void onSlotCountChanged(int slotCount) override;
+#endif
// releaseBufferLocked overrides the ConsumerBase method to update the
// mEglSlots array in addition to the ConsumerBase.
- virtual status_t releaseBufferLocked(int slot,
- const sp<GraphicBuffer> graphicBuffer,
- EGLDisplay display, EGLSyncKHR eglFence) override;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer,
+ EGLDisplay display = EGL_NO_DISPLAY,
+ EGLSyncKHR eglFence = EGL_NO_SYNC_KHR) override;
status_t releaseBufferLocked(int slot,
const sp<GraphicBuffer> graphicBuffer, EGLSyncKHR eglFence) {
return releaseBufferLocked(slot, graphicBuffer, mEglDisplay, eglFence);
}
+#endif
struct PendingRelease {
- PendingRelease() : isPending(false), currentTexture(-1),
- graphicBuffer(), display(nullptr), fence(nullptr) {}
+ PendingRelease() : isPending(false), currentTexture(-1), graphicBuffer() {}
bool isPending;
int currentTexture;
sp<GraphicBuffer> graphicBuffer;
- EGLDisplay display;
- EGLSyncKHR fence;
};
// This releases the buffer in the slot referenced by mCurrentTexture,
@@ -465,16 +467,18 @@ private:
// EGLSlot contains the information and object references that
// GLConsumer maintains about a BufferQueue buffer slot.
struct EglSlot {
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
EglSlot() : mEglFence(EGL_NO_SYNC_KHR) {}
-
+#endif
// mEglImage is the EGLImage created from mGraphicBuffer.
sp<EglImage> mEglImage;
-
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
// mFence is the EGL sync object that must signal before the buffer
// associated with this buffer slot may be dequeued. It is initialized
// to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based
// on a compile-time option) set to a new sync object in updateTexImage.
EGLSyncKHR mEglFence;
+#endif
};
// mEglDisplay is the EGLDisplay with which this GLConsumer is currently
@@ -496,8 +500,11 @@ private:
// slot that has not yet been used. The buffer allocated to a slot will also
// be replaced if the requested buffer usage or geometry differs from that
// of the buffer allocated to a slot.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ std::vector<EglSlot> mEglSlots;
+#else
EglSlot mEglSlots[BufferQueueDefs::NUM_BUFFER_SLOTS];
-
+#endif
// mCurrentTexture is the buffer slot index of the buffer that is currently
// bound to the OpenGL texture. It is initialized to INVALID_BUFFER_SLOT,
// indicating that no buffer slot is currently bound to the texture. Note,
diff --git a/libs/gui/include/gui/IConsumerListener.h b/libs/gui/include/gui/IConsumerListener.h
index 51d3959de7..95e66c778e 100644
--- a/libs/gui/include/gui/IConsumerListener.h
+++ b/libs/gui/include/gui/IConsumerListener.h
@@ -16,9 +16,6 @@
#pragma once
-#include <binder/IInterface.h>
-#include <binder/SafeInterface.h>
-
#include <utils/Errors.h>
#include <utils/RefBase.h>
@@ -98,27 +95,18 @@ public:
virtual void onSetFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
int8_t /*changeFrameRateStrategy*/) {}
#endif
-};
-
-#ifndef NO_BINDER
-class IConsumerListener : public ConsumerListener, public IInterface {
-public:
- DECLARE_META_INTERFACE(ConsumerListener)
-};
-
-class BnConsumerListener : public SafeBnInterface<IConsumerListener> {
-public:
- BnConsumerListener() : SafeBnInterface<IConsumerListener>("BnConsumerListener") {}
- status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
- uint32_t flags = 0) override;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ // Notifies the consumer that IGraphicBufferProducer::extendSlotCount has
+ // been called and the total slot count has increased.
+ //
+ // This will only ever be called if
+ // IGraphicBufferConsumer::allowUnlimitedSlots has been called on the
+ // consumer.
+ virtual void onSlotCountChanged(int /* slotCount */) {}
+#endif
};
-#else
-class IConsumerListener : public ConsumerListener {
-};
-class BnConsumerListener : public IConsumerListener {
-};
-#endif
+class IConsumerListener : public ConsumerListener {};
} // namespace android
diff --git a/libs/gui/include/gui/IGraphicBufferConsumer.h b/libs/gui/include/gui/IGraphicBufferConsumer.h
index 0b92e7df62..8272a591da 100644
--- a/libs/gui/include/gui/IGraphicBufferConsumer.h
+++ b/libs/gui/include/gui/IGraphicBufferConsumer.h
@@ -16,6 +16,7 @@
#pragma once
+#include <com_android_graphics_libgui_flags.h>
#include <gui/OccupancyTracker.h>
#include <binder/IInterface.h>
@@ -35,15 +36,12 @@ class Fence;
class GraphicBuffer;
class IConsumerListener;
class NativeHandle;
-#ifndef NO_BINDER
-class IGraphicBufferConsumer : public IInterface {
-public:
- DECLARE_META_INTERFACE(GraphicBufferConsumer)
-#else
+
+/*
+ * See IGraphicBufferProducer for details on SLOT_COUNT.
+ */
class IGraphicBufferConsumer : public RefBase {
public:
-#endif
-
enum {
// Returned by releaseBuffer, after which the consumer must free any references to the
// just-released buffer that it might have.
@@ -92,7 +90,7 @@ public:
//
// Return of a value other than NO_ERROR means an error has occurred:
// * BAD_VALUE - the given slot number is invalid, either because it is out of the range
- // [0, NUM_BUFFER_SLOTS) or because the slot it refers to is not
+ // [0, SLOT_COUNT) or because the slot it refers to is not
// currently acquired.
virtual status_t detachBuffer(int slot) = 0;
@@ -134,19 +132,17 @@ public:
// * the buffer slot was invalid
// * the fence was NULL
// * the buffer slot specified is not in the acquired state
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber,
+ const sp<Fence>& releaseFence) = 0;
+#else
virtual status_t releaseBuffer(int buf, uint64_t frameNumber, EGLDisplay display,
EGLSyncKHR fence, const sp<Fence>& releaseFence) = 0;
- status_t releaseHelper(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) {
+ status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) {
return releaseBuffer(buf, frameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, releaseFence);
}
- // This is explicitly *not* the actual signature of IGBC::releaseBuffer, but:
- // 1) We have no easy way to send the EGL objects across Binder
- // 2) This has always been broken, probably because
- // 3) IGBC is rarely remoted
- // For now, we will choose to bury our heads in the sand and ignore this problem until such time
- // as we can finally finish converting away from EGL sync to native Android sync
- using ReleaseBuffer = decltype(&IGraphicBufferConsumer::releaseHelper);
+#endif
// consumerConnect connects a consumer to the BufferQueue. Only one consumer may be connected,
// and when that consumer disconnects the BufferQueue is placed into the "abandoned" state,
@@ -180,6 +176,19 @@ public:
// * NO_INIT - the BufferQueue has been abandoned.
virtual status_t getReleasedBuffers(uint64_t* slotMask) = 0;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ // getReleasedBuffersExtended for each slot, sets slotMask[slot] to 1 if it
+ // corresponds to a released buffer slot. In particular, a released buffer
+ // is one that has been released by the BufferQueue but has not yet been
+ // released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the BufferQueue has been abandoned.
+ virtual status_t getReleasedBuffersExtended(std::vector<bool>* slotMask) = 0;
+#endif
+
// setDefaultBufferSize is used to set the size of buffers returned by dequeueBuffer when a
// width and height of zero is requested. Default is 1x1.
//
@@ -187,6 +196,26 @@ public:
// * BAD_VALUE - either w or h was zero
virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) = 0;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ // allowUnlimitedSlots allows the producer to set the upper bound on slots.
+ //
+ // Must be called before the producer is connected. If the producer
+ // increases the slot count, an IConsumerListener::onSlotCountChanged
+ // update is sent.
+ //
+ // This can not be used with setMaxBufferCount. Calls after
+ // setMaxBufferCount will fail and calls to setMaxBufferCount after setting
+ // this to true will fail.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the BufferQueue has been abandoned
+ // * INVALID_OPERATION - one of the following errors has occurred:
+ // * Producer has been connected
+ // * setMaxBufferCount has been called and shrunk the
+ // BufferQueue.
+ virtual status_t allowUnlimitedSlots(bool allowUnlimitedSlots) = 0;
+#endif
+
// setMaxBufferCount sets the maximum value for the number of buffers used in the BufferQueue
// (the initial default is NUM_BUFFER_SLOTS). If a call to setMaxAcquiredBufferCount (by the
// consumer), or a call to setAsyncMode or setMaxDequeuedBufferCount (by the producer), would
@@ -286,18 +315,4 @@ public:
}
};
-#ifndef NO_BINDER
-class BnGraphicBufferConsumer : public SafeBnInterface<IGraphicBufferConsumer> {
-public:
- BnGraphicBufferConsumer()
- : SafeBnInterface<IGraphicBufferConsumer>("BnGraphicBufferConsumer") {}
-
- status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
- uint32_t flags = 0) override;
-};
-#else
-class BnGraphicBufferConsumer : public IGraphicBufferConsumer {
-};
-#endif
-
} // namespace android
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 197e792367..7accca6298 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -19,6 +19,7 @@
#include <stdint.h>
#include <sys/types.h>
+#include <optional>
#include <utils/Errors.h>
#include <utils/RefBase.h>
@@ -28,6 +29,7 @@
#include <ui/BufferQueueDefs.h>
#include <ui/Fence.h>
#include <ui/GraphicBuffer.h>
+#include <ui/PictureProfileHandle.h>
#include <ui/Rect.h>
#include <ui/Region.h>
@@ -70,6 +72,14 @@ using HGraphicBufferProducerV2_0 =
* dequeueBuffer() to get an empty buffer, fills it with data, then
* calls queueBuffer() to make it available to the consumer.
*
+ * BufferQueues have a size, which we'll refer to in other comments as
+ * SLOT_COUNT. Its default is 64 (NUM_BUFFER_SLOTS). It can be adjusted by
+ * the IGraphicBufferConsumer::setMaxBufferCount, or when
+ * IGraphicBufferConsumer::allowUnlimitedSlots is set to true, by
+ * IGraphicBufferProducer::extendSlotCount. The actual number of buffers in use
+ * is a function of various configurations, including whether we're in single
+ * buffer mode, the maximum dequeuable/aquirable buffers, and SLOT_COUNT.
+ *
* This class was previously called ISurfaceTexture.
*/
#ifndef NO_BINDER
@@ -104,7 +114,7 @@ public:
// slot->buffer mapping so that it's not necessary to transfer a
// GraphicBuffer for every dequeue operation.
//
- // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+ // The slot must be in the range of [0, SLOT_COUNT).
//
// Return of a value other than NO_ERROR means an error has occurred:
// * NO_INIT - the buffer queue has been abandoned or the producer is not
@@ -114,6 +124,30 @@ public:
// * buffer specified by the slot is not dequeued
virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) = 0;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ // extendSlotCount sets the maximum slot count (SLOT_COUNT) to the given
+ // size. This feature must be enabled by the consumer to function via
+ // IGraphicBufferConsumer::allowUnlimitedSlots. This must be called before
+ // the producer connects.
+ //
+ // After calling this, any slot can be returned in the [0, size) range.
+ // Callers are responsible for the allocation of the appropriate slots
+ // array for their own buffer cache.
+ //
+ // On success, the consumer is notified (so that it can increase its own
+ // slot cache).
+ //
+ // Return of a value other than NO_ERROR means that an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned
+ // * INVALID_OPERATION - one of the following conditions has occurred:
+ // * The producer is connected already
+ // * The consumer didn't call allowUnlimitedSlots
+ // * BAD_VALUE - The value is smaller than the previous max size
+ // (initialized to 64, then whatever the last call to this
+ // was)
+ virtual status_t extendSlotCount(int size);
+#endif
+
// setMaxDequeuedBufferCount sets the maximum number of buffers that can be
// dequeued by the producer at one time. If this method succeeds, any new
// buffer slots will be both unallocated and owned by the BufferQueue object
@@ -127,7 +161,7 @@ public:
// will result in a BAD_VALUE error.
//
// The buffer count should be at least 1 (inclusive), but at most
- // (NUM_BUFFER_SLOTS - the minimum undequeued buffer count) (exclusive). The
+ // (SLOT_COUNT - the minimum undequeued buffer count) (exclusive). The
// minimum undequeued buffer count can be obtained by calling
// query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS).
//
@@ -237,8 +271,8 @@ public:
// * NO_INIT - the buffer queue has been abandoned or the producer is not
// connected.
// * BAD_VALUE - the given slot number is invalid, either because it is
- // out of the range [0, NUM_BUFFER_SLOTS), or because the slot
- // it refers to is not currently dequeued and requested.
+ // out of the range [0, SLOT_COUNT), or because the slot it
+ // refers to is not currently dequeued and requested.
virtual status_t detachBuffer(int slot) = 0;
// detachNextBuffer is equivalent to calling dequeueBuffer, requestBuffer,
@@ -365,6 +399,14 @@ public:
const HdrMetadata& getHdrMetadata() const { return hdrMetadata; }
void setHdrMetadata(const HdrMetadata& metadata) { hdrMetadata = metadata; }
+ const std::optional<PictureProfileHandle>& getPictureProfileHandle() const {
+ return pictureProfileHandle;
+ }
+ void setPictureProfileHandle(const PictureProfileHandle& profile) {
+ pictureProfileHandle = profile;
+ }
+ void clearPictureProfileHandle() { pictureProfileHandle = std::nullopt; }
+
int64_t timestamp{0};
int isAutoTimestamp{0};
android_dataspace dataSpace{HAL_DATASPACE_UNKNOWN};
@@ -377,6 +419,7 @@ public:
bool getFrameTimestamps{false};
int slot{-1};
HdrMetadata hdrMetadata;
+ std::optional<PictureProfileHandle> pictureProfileHandle;
};
struct QueueBufferOutput : public Flattenable<QueueBufferOutput> {
@@ -404,6 +447,7 @@ public:
FrameEventHistoryDelta frameTimestamps;
bool bufferReplaced{false};
int maxBufferCount{BufferQueueDefs::NUM_BUFFER_SLOTS};
+ bool isSlotExpansionAllowed{false};
status_t result{NO_ERROR};
};
@@ -419,7 +463,7 @@ public:
// below). Any other properties (zero point, etc)
// are client-dependent, and should be documented by the client.
//
- // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+ // The slot must be in the range of [0, SLOT_COUNT).
//
// Upon success, the output will be filled with meaningful values
// (refer to the documentation below).
@@ -449,7 +493,7 @@ public:
//
// The buffer is not queued for use by the consumer.
//
- // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+ // The slot must be in the range of [0, SLOT_COUNT).
//
// The buffer will not be overwritten until the fence signals. The fence
// will usually be the one obtained from dequeueBuffer.
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 2cdde3255e..c2680a42dc 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -26,6 +26,7 @@
#include <android/gui/LayerCaptureArgs.h>
#include <android/gui/TrustedPresentationThresholds.h>
#include <android/native_window.h>
+#include <gui/DisplayLuts.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/ITransactionCompletedListener.h>
#include <math/mat4.h>
@@ -46,6 +47,7 @@
#include <ui/BlurRegion.h>
#include <ui/GraphicTypes.h>
#include <ui/LayerStack.h>
+#include <ui/PictureProfileHandle.h>
#include <ui/Rect.h>
#include <ui/Region.h>
#include <ui/Rotation.h>
@@ -169,6 +171,10 @@ struct layer_state_t {
// Sets a property on this layer indicating that its visible region should be considered
// when computing TrustedPresentation Thresholds.
eCanOccludePresentation = 0x1000,
+ // Indicates that the SurfaceControl should recover from buffer stuffing when
+ // possible. This is the case when the SurfaceControl is the root SurfaceControl
+ // owned by ViewRootImpl.
+ eRecoverableFromBufferStuffing = 0x2000,
};
enum {
@@ -184,6 +190,7 @@ struct layer_state_t {
eCachingHintChanged = 0x00000200,
eDimmingEnabledChanged = 0x00000400,
eShadowRadiusChanged = 0x00000800,
+ eLutsChanged = 0x00001000,
eBufferCropChanged = 0x00002000,
eRelativeLayerChanged = 0x00004000,
eReparent = 0x00008000,
@@ -222,6 +229,9 @@ struct layer_state_t {
eExtendedRangeBrightnessChanged = 0x10000'00000000,
eEdgeExtensionChanged = 0x20000'00000000,
eBufferReleaseChannelChanged = 0x40000'00000000,
+ ePictureProfileHandleChanged = 0x80000'00000000,
+ eAppContentPriorityChanged = 0x100000'00000000,
+ eClientDrawnCornerRadiusChanged = 0x200000'00000000,
};
layer_state_t();
@@ -242,9 +252,9 @@ struct layer_state_t {
// Geometry updates.
static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::eBufferCropChanged |
layer_state_t::eBufferTransformChanged | layer_state_t::eCornerRadiusChanged |
- layer_state_t::eCropChanged | layer_state_t::eDestinationFrameChanged |
- layer_state_t::eMatrixChanged | layer_state_t::ePositionChanged |
- layer_state_t::eTransformToDisplayInverseChanged |
+ layer_state_t::eClientDrawnCornerRadiusChanged | layer_state_t::eCropChanged |
+ layer_state_t::eDestinationFrameChanged | layer_state_t::eMatrixChanged |
+ layer_state_t::ePositionChanged | layer_state_t::eTransformToDisplayInverseChanged |
layer_state_t::eTransparentRegionChanged | layer_state_t::eEdgeExtensionChanged;
// Buffer and related updates.
@@ -255,7 +265,7 @@ struct layer_state_t {
layer_state_t::eTransformToDisplayInverseChanged |
layer_state_t::eTransparentRegionChanged |
layer_state_t::eExtendedRangeBrightnessChanged |
- layer_state_t::eDesiredHdrHeadroomChanged;
+ layer_state_t::eDesiredHdrHeadroomChanged | layer_state_t::eLutsChanged;
// Content updates.
static constexpr uint64_t CONTENT_CHANGES = layer_state_t::BUFFER_CHANGES |
@@ -265,7 +275,8 @@ struct layer_state_t {
layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged |
layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged |
layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged |
- layer_state_t::eStretchChanged;
+ layer_state_t::eStretchChanged |
+ layer_state_t::ePictureProfileHandleChanged | layer_state_t::eAppContentPriorityChanged;
// Changes which invalidates the layer's visible region in CE.
static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES |
@@ -290,6 +301,11 @@ struct layer_state_t {
static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES |
layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged;
+ // Changes that force GPU composition.
+ static constexpr uint64_t COMPOSITION_EFFECTS = layer_state_t::eBackgroundBlurRadiusChanged |
+ layer_state_t::eBlurRegionsChanged | layer_state_t::eCornerRadiusChanged |
+ layer_state_t::eShadowRadiusChanged | layer_state_t::eStretchChanged;
+
bool hasValidBuffer() const;
void sanitize(int32_t permissions);
@@ -318,6 +334,7 @@ struct layer_state_t {
uint8_t reserved;
matrix22_t matrix;
float cornerRadius;
+ float clientDrawnCornerRadius;
uint32_t backgroundBlurRadius;
sp<SurfaceControl> relativeLayerSurfaceControl;
@@ -330,7 +347,7 @@ struct layer_state_t {
Region transparentRegion;
uint32_t bufferTransform;
bool transformToDisplayInverse;
- Rect crop;
+ FloatRect crop;
std::shared_ptr<BufferData> bufferData = nullptr;
ui::Dataspace dataspace;
HdrMetadata hdrMetadata;
@@ -410,12 +427,23 @@ struct layer_state_t {
float currentHdrSdrRatio = 1.f;
float desiredHdrSdrRatio = 1.f;
+ // Enhance the quality of the buffer contents by configurating a picture processing pipeline
+ // with values as specified by this picture profile.
+ PictureProfileHandle pictureProfileHandle{PictureProfileHandle::NONE};
+
+ // A value indicating the significance of the layer's content to the app's desired user
+ // experience. A higher value will result in more likelihood of getting access to limited
+ // resources, such as picture processing hardware.
+ int32_t appContentPriority = 0;
+
gui::CachingHint cachingHint = gui::CachingHint::Enabled;
TrustedPresentationThresholds trustedPresentationThresholds;
TrustedPresentationListener trustedPresentationListener;
std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> bufferReleaseChannel;
+
+ std::shared_ptr<gui::DisplayLuts> luts;
};
class ComposerState {
@@ -474,12 +502,18 @@ struct DisplayState {
Rect layerStackSpaceRect = Rect::EMPTY_RECT;
Rect orientedDisplaySpaceRect = Rect::EMPTY_RECT;
- // Exclusive to virtual displays: The sink surface into which the virtual display is rendered,
- // and an optional resolution that overrides its default dimensions.
- sp<IGraphicBufferProducer> surface;
+ // For physical displays, this is the resolution, which must match the active display mode. To
+ // change the resolution, the client must first call SurfaceControl.setDesiredDisplayModeSpecs
+ // with the new DesiredDisplayModeSpecs#defaultMode, then commit the matching width and height.
+ //
+ // For virtual displays, this is an optional resolution that overrides its default dimensions.
+ //
uint32_t width = 0;
uint32_t height = 0;
+ // For virtual displays, this is the sink surface into which the virtual display is rendered.
+ sp<IGraphicBufferProducer> surface;
+
status_t write(Parcel& output) const;
status_t read(const Parcel& input);
};
diff --git a/libs/gui/include/gui/StreamSplitter.h b/libs/gui/include/gui/StreamSplitter.h
index b4eef292c1..28237b6940 100644
--- a/libs/gui/include/gui/StreamSplitter.h
+++ b/libs/gui/include/gui/StreamSplitter.h
@@ -37,7 +37,7 @@ class IGraphicBufferProducer;
// BufferQueue, where each buffer queued to the input is available to be
// acquired by each of the outputs, and is able to be dequeued by the input
// again only once all of the outputs have released it.
-class StreamSplitter : public BnConsumerListener {
+class StreamSplitter : public IConsumerListener {
public:
// createSplitter creates a new splitter, outSplitter, using inputQueue as
// the input BufferQueue. Output BufferQueues must be added using addOutput
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 14a351316d..755674d9e6 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -558,7 +558,11 @@ protected:
// slot that has not yet been used. The buffer allocated to a slot will also
// be replaced if the requested buffer usage or geometry differs from that
// of the buffer allocated to a slot.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ std::vector<BufferSlot> mSlots;
+#else
BufferSlot mSlots[NUM_BUFFER_SLOTS];
+#endif
// mReqWidth is the buffer width that will be requested at the next dequeue
// operation. It is initialized to 1.
@@ -732,6 +736,10 @@ protected:
std::vector<sp<GraphicBuffer>> mRemovedBuffers;
int mMaxBufferCount;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ bool mIsSlotExpansionAllowed;
+#endif
+
sp<IProducerListener> mListenerProxy;
// Get and flush the buffers of given slots, if the buffer in the slot
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 4f9af16826..60f16ae902 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -38,6 +38,7 @@
#include <ui/EdgeExtensionEffect.h>
#include <ui/FrameStats.h>
#include <ui/GraphicTypes.h>
+#include <ui/PictureProfileHandle.h>
#include <ui/PixelFormat.h>
#include <ui/Rotation.h>
#include <ui/StaticDisplayInfo.h>
@@ -297,6 +298,10 @@ public:
static status_t removeHdrLayerInfoListener(const sp<IBinder>& displayToken,
const sp<gui::IHdrLayerInfoListener>& listener);
+ static status_t addActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+
+ static status_t removeActivePictureListener(const sp<gui::IActivePictureListener>& listener);
+
/*
* Sends a power boost to the composer. This function is asynchronous.
*
@@ -342,6 +347,15 @@ public:
static bool flagEdgeExtensionEffectUseShader();
+ /**
+ * Returns how many picture profiles are supported by the display.
+ *
+ * displayToken
+ * The token of the display.
+ */
+ static status_t getMaxLayerPictureProfiles(const sp<IBinder>& displayToken,
+ int32_t* outMaxProfiles);
+
// ------------------------------------------------------------------------
// surface creation / destruction
@@ -437,10 +451,12 @@ public:
static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other);
// Tracks registered callbacks
sp<TransactionCompletedListener> mTransactionCompletedListener = nullptr;
+ // Prints debug logs when enabled.
+ bool mLogCallPoints = false;
protected:
- std::unordered_map<sp<IBinder>, ComposerState, IBinderHash> mComposerStates;
- SortedVector<DisplayState> mDisplayStates;
+ Vector<ComposerState> mComposerStates;
+ Vector<DisplayState> mDisplayStates;
std::unordered_map<sp<ITransactionCompletedListener>, CallbackInfo, TCLHash>
mListenerCallbacks;
std::vector<client_cache_t> mUncacheBuffers;
@@ -451,10 +467,7 @@ public:
std::vector<uint64_t> mMergedTransactionIds;
uint64_t mId;
-
- bool mAnimation = false;
- bool mEarlyWakeupStart = false;
- bool mEarlyWakeupEnd = false;
+ uint32_t mFlags = 0;
// Indicates that the Transaction may contain buffers that should be cached. The reason this
// is only a guess is that buffers can be removed before cache is called. This is only a
@@ -549,7 +562,13 @@ public:
Transaction& setMatrix(const sp<SurfaceControl>& sc,
float dsdx, float dtdx, float dtdy, float dsdy);
Transaction& setCrop(const sp<SurfaceControl>& sc, const Rect& crop);
+ Transaction& setCrop(const sp<SurfaceControl>& sc, const FloatRect& crop);
Transaction& setCornerRadius(const sp<SurfaceControl>& sc, float cornerRadius);
+ // Sets the client drawn corner radius for the layer. If both a corner radius and a client
+ // radius are sent to SF, the client radius will be used. This indicates that the corner
+ // radius is drawn by the client and not SurfaceFlinger.
+ Transaction& setClientDrawnCornerRadius(const sp<SurfaceControl>& sc,
+ float clientDrawnCornerRadius);
Transaction& setBackgroundBlurRadius(const sp<SurfaceControl>& sc,
int backgroundBlurRadius);
Transaction& setBlurRegions(const sp<SurfaceControl>& sc,
@@ -601,6 +620,11 @@ public:
Transaction& setExtendedRangeBrightness(const sp<SurfaceControl>& sc,
float currentBufferRatio, float desiredRatio);
Transaction& setDesiredHdrHeadroom(const sp<SurfaceControl>& sc, float desiredRatio);
+ Transaction& setLuts(const sp<SurfaceControl>& sc, base::unique_fd&& lutFd,
+ const std::vector<int32_t>& offsets,
+ const std::vector<int32_t>& dimensions,
+ const std::vector<int32_t>& sizes,
+ const std::vector<int32_t>& samplingKeys);
Transaction& setCachingHint(const sp<SurfaceControl>& sc, gui::CachingHint cachingHint);
Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata);
Transaction& setSurfaceDamageRegion(const sp<SurfaceControl>& sc,
@@ -679,7 +703,8 @@ public:
// ONLY FOR BLAST ADAPTER
Transaction& notifyProducerDisconnect(const sp<SurfaceControl>& sc);
- Transaction& setInputWindowInfo(const sp<SurfaceControl>& sc, const gui::WindowInfo& info);
+ Transaction& setInputWindowInfo(const sp<SurfaceControl>& sc,
+ sp<gui::WindowInfoHandle> info);
Transaction& setFocusedWindow(const gui::FocusRequest& request);
Transaction& addWindowInfosReportedListener(
@@ -767,6 +792,20 @@ public:
const sp<SurfaceControl>& sc,
const std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint>& channel);
+ /**
+ * Configures a surface control to use picture processing hardware, configured as specified
+ * by the picture profile, to enhance the quality of all subsequent buffer contents.
+ */
+ Transaction& setPictureProfileHandle(const sp<SurfaceControl>& sc,
+ const PictureProfileHandle& pictureProfileHandle);
+
+ /**
+ * Configures the relative importance of the contents of the layer with respect to the app's
+ * user experience. A lower priority value will give the layer preferred access to limited
+ * resources, such as picture processing, over a layer with a higher priority value.
+ */
+ Transaction& setContentPriority(const sp<SurfaceControl>& sc, int32_t contentPriority);
+
status_t setDisplaySurface(const sp<IBinder>& token,
const sp<IGraphicBufferProducer>& bufferProducer);
@@ -803,6 +842,7 @@ public:
static void setDefaultApplyToken(sp<IBinder> applyToken);
static status_t sendSurfaceFlushJankDataTransaction(const sp<SurfaceControl>& sc);
+ void enableDebugLogCallPoints();
};
status_t clearLayerFrameStats(const sp<IBinder>& token) const;
diff --git a/libs/gui/include/gui/VsyncEventData.h b/libs/gui/include/gui/VsyncEventData.h
index b40a84099c..ced5130505 100644
--- a/libs/gui/include/gui/VsyncEventData.h
+++ b/libs/gui/include/gui/VsyncEventData.h
@@ -36,6 +36,9 @@ struct VsyncEventData {
// Size of frame timelines provided by the platform; max is kFrameTimelinesCapacity.
uint32_t frameTimelinesLength;
+ // Number of queued buffers to indicate if buffer stuffing mode is detected.
+ uint32_t numberQueuedBuffers;
+
struct alignas(8) FrameTimeline {
// The Vsync Id corresponsing to this vsync event. This will be used to
// populate ISurfaceComposer::setFrameTimelineVsync and
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index eb3be5588a..420dc2103f 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -150,8 +150,6 @@ struct WindowInfo : public Parcelable {
static_cast<uint32_t>(os::InputConfig::NOT_FOCUSABLE),
NOT_TOUCHABLE =
static_cast<uint32_t>(os::InputConfig::NOT_TOUCHABLE),
- PREVENT_SPLITTING =
- static_cast<uint32_t>(os::InputConfig::PREVENT_SPLITTING),
DUPLICATE_TOUCH_TO_WALLPAPER =
static_cast<uint32_t>(os::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER),
IS_WALLPAPER =
@@ -220,9 +218,14 @@ struct WindowInfo : public Parcelable {
// An alpha of 1.0 means fully opaque and 0.0 means fully transparent.
float alpha;
- // Transform applied to individual windows.
+ // Transform applied to individual windows for input.
+ // Maps display coordinates to the window's input coordinate space.
ui::Transform transform;
+ // Transform applied to get to the layer stack space of the cloned window for input.
+ // Maps display coordinates of the clone window to the layer stack space of the cloned window.
+ std::optional<ui::Transform> cloneLayerStackTransform;
+
/*
* This is filled in by the WM relative to the frame and then translated
* to absolute coordinates by SurfaceFlinger once the frame is computed.
diff --git a/libs/gui/include/gui/mock/GraphicBufferConsumer.h b/libs/gui/include/gui/mock/GraphicBufferConsumer.h
index 98f24c2d44..24d26b12a1 100644
--- a/libs/gui/include/gui/mock/GraphicBufferConsumer.h
+++ b/libs/gui/include/gui/mock/GraphicBufferConsumer.h
@@ -18,6 +18,7 @@
#include <gmock/gmock.h>
+#include <com_android_graphics_libgui_flags.h>
#include <gui/IGraphicBufferConsumer.h>
#include <utils/RefBase.h>
@@ -25,18 +26,24 @@
namespace android {
namespace mock {
-class GraphicBufferConsumer : public BnGraphicBufferConsumer, public virtual android::RefBase {
+class GraphicBufferConsumer : public IGraphicBufferConsumer {
public:
GraphicBufferConsumer();
- ~GraphicBufferConsumer() override;
+ ~GraphicBufferConsumer();
MOCK_METHOD3(acquireBuffer, status_t(BufferItem*, nsecs_t, uint64_t));
MOCK_METHOD1(detachBuffer, status_t(int));
MOCK_METHOD2(attachBuffer, status_t(int*, const sp<GraphicBuffer>&));
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ MOCK_METHOD3(releaseBuffer, status_t(int, uint64_t, const sp<Fence>&));
+#else
MOCK_METHOD5(releaseBuffer, status_t(int, uint64_t, EGLDisplay, EGLSyncKHR, const sp<Fence>&));
+#endif
MOCK_METHOD2(consumerConnect, status_t(const sp<IConsumerListener>&, bool));
MOCK_METHOD0(consumerDisconnect, status_t());
+ MOCK_METHOD1(allowUnlimitedSlots, status_t(bool));
MOCK_METHOD1(getReleasedBuffers, status_t(uint64_t*));
+ MOCK_METHOD1(getReleasedBuffersExtended, status_t(std::vector<bool>*));
MOCK_METHOD2(setDefaultBufferSize, status_t(uint32_t, uint32_t));
MOCK_METHOD1(setMaxBufferCount, status_t(int));
MOCK_METHOD1(setMaxAcquiredBufferCount, status_t(int));
diff --git a/libs/gui/include/gui/view/Surface.h b/libs/gui/include/gui/view/Surface.h
index 7ddac8139a..bd8704ddc7 100644
--- a/libs/gui/include/gui/view/Surface.h
+++ b/libs/gui/include/gui/view/Surface.h
@@ -24,7 +24,9 @@
#include <binder/IBinder.h>
#include <binder/Parcelable.h>
+#include <gui/Flags.h>
#include <gui/IGraphicBufferProducer.h>
+#include <gui/Surface.h>
namespace android {
@@ -46,6 +48,30 @@ class Surface : public Parcelable {
sp<IGraphicBufferProducer> graphicBufferProducer;
sp<IBinder> surfaceControlHandle;
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+ // functions used to convert to a parcelable Surface so it can be passed over binder.
+ static Surface fromSurface(const sp<android::Surface>& surface);
+ sp<android::Surface> toSurface() const;
+
+ status_t getUniqueId(/* out */ uint64_t* id) const;
+
+ bool isEmpty() const;
+
+ bool operator==(const Surface& other) const {
+ return graphicBufferProducer == other.graphicBufferProducer;
+ }
+ bool operator!=(const Surface& other) const { return !(*this == other); }
+ bool operator==(const sp<android::Surface> other) const {
+ if (other == nullptr) return graphicBufferProducer == nullptr;
+ return graphicBufferProducer == other->getIGraphicBufferProducer();
+ }
+ bool operator!=(const sp<android::Surface> other) const { return !(*this == other); }
+ bool operator<(const Surface& other) const {
+ return graphicBufferProducer < other.graphicBufferProducer;
+ }
+ bool operator>(const Surface& other) const { return other < *this; }
+#endif
+
virtual status_t writeToParcel(Parcel* parcel) const override;
virtual status_t readFromParcel(const Parcel* parcel) override;
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index d3f2899ba3..90d91ac06a 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -2,6 +2,14 @@ package: "com.android.graphics.libgui.flags"
container: "system"
flag {
+ name: "apply_picture_profiles"
+ namespace: "tv_os_media"
+ description: "This flag controls sending picture profiles from BBQ to Composer HAL"
+ bug: "337330263"
+ is_fixed_read_only: true
+} # apply_picture_profiles
+
+flag {
name: "bq_setframerate"
namespace: "core_graphics"
description: "This flag controls plumbing setFrameRate thru BufferQueue"
@@ -109,9 +117,36 @@ flag {
} # wb_libcameraservice
flag {
+ name: "wb_unlimited_slots"
+ namespace: "core_graphics"
+ description: "Adds APIs and updates the implementation of bufferqueues to have a user-defined slot count."
+ bug: "341359814"
+ is_fixed_read_only: true
+} # wb_unlimited_slots
+
+flag {
name: "bq_producer_throttles_only_async_mode"
namespace: "core_graphics"
description: "BufferQueueProducer only CPU throttle on queueBuffer() in async mode."
bug: "359252619"
is_fixed_read_only: true
} # bq_producer_throttles_only_async_mode
+
+flag {
+ name: "bq_gl_fence_cleanup"
+ namespace: "core_graphics"
+ description: "Remove BufferQueueProducer::dequeue's wait on this fence (or the fence entirely) to prevent deadlocks"
+ bug: "339705065"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} # bq_gl_fence_cleanup
+
+flag {
+ name: "wb_media_migration"
+ namespace: "core_graphics"
+ description: "Main flag for the warren buffers media migration."
+ bug: "340934031"
+ is_fixed_read_only: true
+} # wb_media_migration
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index f07747f32f..87051a7aac 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -55,6 +55,7 @@ cc_test {
"-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_EXTENDEDALLOCATE=true",
"-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_WB_CONSUMER_BASE_OWNS_BQ=true",
"-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_WB_PLATFORM_API_IMPROVEMENTS=true",
+ "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_WB_UNLIMITED_SLOTS=true",
],
srcs: [
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index 53f4a36c42..b861c6d4b7 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -81,7 +81,9 @@ class TestBLASTBufferQueue : public BLASTBufferQueue {
public:
TestBLASTBufferQueue(const std::string& name, const sp<SurfaceControl>& surface, int width,
int height, int32_t format)
- : BLASTBufferQueue(name, surface, width, height, format) {}
+ : BLASTBufferQueue(name) {
+ update(surface, width, height, format);
+ }
void transactionCallback(nsecs_t latchTime, const sp<Fence>& presentFence,
const std::vector<SurfaceControlStats>& stats) override {
@@ -112,8 +114,8 @@ private:
class BLASTBufferQueueHelper {
public:
BLASTBufferQueueHelper(const sp<SurfaceControl>& sc, int width, int height) {
- mBlastBufferQueueAdapter = new TestBLASTBufferQueue("TestBLASTBufferQueue", sc, width,
- height, PIXEL_FORMAT_RGBA_8888);
+ mBlastBufferQueueAdapter = sp<TestBLASTBufferQueue>::make("TestBLASTBufferQueue", sc, width,
+ height, PIXEL_FORMAT_RGBA_8888);
}
void update(const sp<SurfaceControl>& sc, int width, int height) {
diff --git a/libs/gui/tests/BufferItemConsumer_test.cpp b/libs/gui/tests/BufferItemConsumer_test.cpp
index 3b6a66efe9..b980f882ff 100644
--- a/libs/gui/tests/BufferItemConsumer_test.cpp
+++ b/libs/gui/tests/BufferItemConsumer_test.cpp
@@ -22,8 +22,11 @@
#include <gui/BufferItemConsumer.h>
#include <gui/IProducerListener.h>
#include <gui/Surface.h>
+#include <ui/BufferQueueDefs.h>
#include <ui/GraphicBuffer.h>
+#include <unordered_set>
+
namespace android {
static constexpr int kWidth = 100;
@@ -57,14 +60,17 @@ class BufferItemConsumerTest : public ::testing::Test {
};
void SetUp() override {
- mBIC = new BufferItemConsumer(kUsage, kMaxLockedBuffers, true);
+ mBuffers.resize(BufferQueueDefs::NUM_BUFFER_SLOTS);
+
+ sp<Surface> surface;
+ std::tie(mBIC, surface) = BufferItemConsumer::create(kUsage, kMaxLockedBuffers, true);
String8 name("BufferItemConsumer_Under_Test");
mBIC->setName(name);
mBFL = new BufferFreedListener(this);
mBIC->setBufferFreedListener(mBFL);
sp<IProducerListener> producerListener = new TrackingProducerListener(this);
- mProducer = mBIC->getSurface()->getIGraphicBufferProducer();
+ mProducer = surface->getIGraphicBufferProducer();
IGraphicBufferProducer::QueueBufferOutput bufferOutput;
ASSERT_EQ(NO_ERROR,
mProducer->connect(producerListener, NATIVE_WINDOW_API_CPU,
@@ -137,6 +143,11 @@ class BufferItemConsumerTest : public ::testing::Test {
ASSERT_EQ(NO_ERROR, ret);
}
+ void DetachBuffer(int slot) {
+ ALOGD("detachBuffer: slot=%d", slot);
+ status_t ret = mBIC->detachBuffer(mBuffers[slot]);
+ ASSERT_EQ(NO_ERROR, ret);
+ }
std::mutex mMutex;
int mFreedBufferCount{0};
@@ -146,7 +157,7 @@ class BufferItemConsumerTest : public ::testing::Test {
sp<BufferFreedListener> mBFL;
sp<IGraphicBufferProducer> mProducer;
sp<IGraphicBufferConsumer> mConsumer;
- sp<GraphicBuffer> mBuffers[BufferQueueDefs::NUM_BUFFER_SLOTS];
+ std::vector<sp<GraphicBuffer>> mBuffers;
};
// Test that detaching buffer from consumer side triggers onBufferFreed.
@@ -239,4 +250,52 @@ TEST_F(BufferItemConsumerTest, DetachBufferWithBuffer) {
}
#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+TEST_F(BufferItemConsumerTest, UnlimitedSlots_AcquireReleaseAll) {
+ ASSERT_EQ(OK, mProducer->extendSlotCount(256));
+ mBuffers.resize(256);
+
+ ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(100));
+
+ std::unordered_set<int> slots;
+ for (int i = 0; i < 100; i++) {
+ int slot;
+ DequeueBuffer(&slot);
+ slots.insert(slot);
+ }
+ EXPECT_EQ(100u, slots.size());
+
+ for (int dequeuedSlot : slots) {
+ QueueBuffer(dequeuedSlot);
+
+ int slot;
+ AcquireBuffer(&slot);
+ ReleaseBuffer(slot);
+ }
+}
+
+TEST_F(BufferItemConsumerTest, UnlimitedSlots_AcquireDetachAll) {
+ ASSERT_EQ(OK, mProducer->extendSlotCount(256));
+ mBuffers.resize(256);
+
+ ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(100));
+
+ std::unordered_set<int> slots;
+ for (int i = 0; i < 100; i++) {
+ int slot;
+ DequeueBuffer(&slot);
+ slots.insert(slot);
+ }
+ EXPECT_EQ(100u, slots.size());
+
+ for (int dequeuedSlot : slots) {
+ QueueBuffer(dequeuedSlot);
+
+ int slot;
+ AcquireBuffer(&slot);
+ DetachBuffer(slot);
+ }
+}
+#endif
+
} // namespace android
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 2e6ffcb57f..cfbb2e7386 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -20,6 +20,8 @@
#include "Constants.h"
#include "MockConsumer.h"
+#include <EGL/egl.h>
+
#include <gui/BufferItem.h>
#include <gui/BufferItemConsumer.h>
#include <gui/BufferQueue.h>
@@ -27,8 +29,10 @@
#include <gui/Surface.h>
#include <ui/GraphicBuffer.h>
+#include <ui/PictureProfileHandle.h>
#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
@@ -42,8 +46,11 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <csignal>
#include <future>
+#include <optional>
#include <thread>
+#include <unordered_map>
#include <com_android_graphics_libgui_flags.h>
@@ -60,6 +67,15 @@ class BufferQueueTest : public ::testing::Test {
public:
protected:
+ void TearDown() override {
+ std::vector<std::function<void()>> teardownFns;
+ teardownFns.swap(mTeardownFns);
+
+ for (auto& fn : teardownFns) {
+ fn();
+ }
+ }
+
void GetMinUndequeuedBufferCount(int* bufferCount) {
ASSERT_TRUE(bufferCount != nullptr);
ASSERT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
@@ -94,6 +110,7 @@ protected:
sp<IGraphicBufferProducer> mProducer;
sp<IGraphicBufferConsumer> mConsumer;
+ std::vector<std::function<void()>> mTeardownFns;
};
static const uint32_t TEST_DATA = 0x12345678u;
@@ -101,12 +118,14 @@ static const uint32_t TEST_DATA = 0x12345678u;
// XXX: Tests that fork a process to hold the BufferQueue must run before tests
// that use a local BufferQueue, or else Binder will get unhappy
//
-// In one instance this was a crash in the createBufferQueue where the
-// binder call to create a buffer allocator apparently got garbage back.
-// See b/36592665.
+// TODO(b/392945118): In one instance this was a crash in the createBufferQueue
+// where the binder call to create a buffer allocator apparently got garbage
+// back. See b/36592665.
TEST_F(BufferQueueTest, DISABLED_BufferQueueInAnotherProcess) {
const String16 PRODUCER_NAME = String16("BQTestProducer");
- const String16 CONSUMER_NAME = String16("BQTestConsumer");
+
+ base::unique_fd readfd, writefd;
+ ASSERT_TRUE(base::Pipe(&readfd, &writefd));
pid_t forkPid = fork();
ASSERT_NE(forkPid, -1);
@@ -118,23 +137,51 @@ TEST_F(BufferQueueTest, DISABLED_BufferQueueInAnotherProcess) {
BufferQueue::createBufferQueue(&producer, &consumer);
sp<IServiceManager> serviceManager = defaultServiceManager();
serviceManager->addService(PRODUCER_NAME, IInterface::asBinder(producer));
- serviceManager->addService(CONSUMER_NAME, IInterface::asBinder(consumer));
+
+ class ChildConsumerListener : public IConsumerListener {
+ public:
+ ChildConsumerListener(const sp<IGraphicBufferConsumer>& consumer,
+ base::unique_fd&& writeFd)
+ : mConsumer(consumer), mWriteFd(std::move(writeFd)) {}
+
+ virtual void onFrameAvailable(const BufferItem&) override {
+ BufferItem item;
+ ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+
+ uint32_t* dataOut;
+ ASSERT_EQ(OK,
+ item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN,
+ reinterpret_cast<void**>(&dataOut)));
+ ASSERT_EQ(*dataOut, TEST_DATA);
+ ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
+
+ bool isOk = true;
+ write(mWriteFd, &isOk, sizeof(bool));
+ }
+ virtual void onBuffersReleased() override {}
+ virtual void onSidebandStreamChanged() override {}
+
+ private:
+ sp<IGraphicBufferConsumer> mConsumer;
+ base::unique_fd mWriteFd;
+ };
+
+ sp<ChildConsumerListener> mc =
+ sp<ChildConsumerListener>::make(consumer, std::move(writefd));
+ ASSERT_EQ(OK, consumer->consumerConnect(mc, false));
+
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
LOG_ALWAYS_FATAL("Shouldn't be here");
+ } else {
+ mTeardownFns.emplace_back([forkPid]() { kill(forkPid, SIGTERM); });
}
sp<IServiceManager> serviceManager = defaultServiceManager();
sp<IBinder> binderProducer = serviceManager->waitForService(PRODUCER_NAME);
mProducer = interface_cast<IGraphicBufferProducer>(binderProducer);
EXPECT_TRUE(mProducer != nullptr);
- sp<IBinder> binderConsumer =
- serviceManager->getService(CONSUMER_NAME);
- mConsumer = interface_cast<IGraphicBufferConsumer>(binderConsumer);
- EXPECT_TRUE(mConsumer != nullptr);
- sp<MockConsumer> mc(new MockConsumer);
- ASSERT_EQ(OK, mConsumer->consumerConnect(mc, false));
IGraphicBufferProducer::QueueBufferOutput output;
ASSERT_EQ(OK,
mProducer->connect(nullptr, NATIVE_WINDOW_API_CPU, false, &output));
@@ -158,14 +205,9 @@ TEST_F(BufferQueueTest, DISABLED_BufferQueueInAnotherProcess) {
NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
- BufferItem item;
- ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
-
- uint32_t* dataOut;
- ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN,
- reinterpret_cast<void**>(&dataOut)));
- ASSERT_EQ(*dataOut, TEST_DATA);
- ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
+ bool isOk;
+ read(readfd, &isOk, sizeof(bool));
+ ASSERT_TRUE(isOk);
}
TEST_F(BufferQueueTest, GetMaxBufferCountInQueueBufferOutput_Succeeds) {
@@ -424,8 +466,7 @@ TEST_F(BufferQueueTest, DetachAndReattachOnConsumerSide) {
ASSERT_EQ(BAD_VALUE, mConsumer->attachBuffer(&newSlot, nullptr));
ASSERT_EQ(OK, mConsumer->attachBuffer(&newSlot, item.mGraphicBuffer));
- ASSERT_EQ(OK, mConsumer->releaseBuffer(newSlot, 0, EGL_NO_DISPLAY,
- EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(newSlot, 0, Fence::NO_FENCE));
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -608,8 +649,7 @@ TEST_F(BufferQueueTest, TestSharedBufferModeWithoutAutoRefresh) {
ASSERT_EQ(true, item.mQueuedBuffer);
ASSERT_EQ(false, item.mAutoRefresh);
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
// attempt to acquire a second time should return no buffer available
ASSERT_EQ(IGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
@@ -652,8 +692,7 @@ TEST_F(BufferQueueTest, TestSharedBufferModeWithAutoRefresh) {
ASSERT_EQ(i == 0, item.mQueuedBuffer);
ASSERT_EQ(true, item.mAutoRefresh);
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
}
// Repeatedly queue and dequeue a buffer from the producer side, it should
@@ -683,8 +722,7 @@ TEST_F(BufferQueueTest, TestSharedBufferModeWithAutoRefresh) {
ASSERT_EQ(i == 0, item.mQueuedBuffer);
ASSERT_EQ(true, item.mAutoRefresh);
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
}
}
@@ -734,8 +772,7 @@ TEST_F(BufferQueueTest, TestSharedBufferModeUsingAlreadyDequeuedBuffer) {
ASSERT_EQ(true, item.mQueuedBuffer);
ASSERT_EQ(false, item.mAutoRefresh);
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
// attempt to acquire a second time should return no buffer available
ASSERT_EQ(IGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
@@ -873,8 +910,7 @@ TEST_F(BufferQueueTest, CanRetrieveLastQueuedBuffer) {
for (size_t i = 0; i < 2; ++i) {
BufferItem item;
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
}
// Make sure we got the second buffer back
@@ -928,8 +964,7 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) {
nullptr, nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
std::this_thread::sleep_for(16ms);
}
@@ -945,8 +980,7 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) {
nullptr, nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
std::this_thread::sleep_for(16ms);
}
ASSERT_EQ(OK,
@@ -958,12 +992,10 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) {
nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
std::this_thread::sleep_for(16ms);
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
// Sleep between segments
std::this_thread::sleep_for(500ms);
@@ -980,13 +1012,11 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) {
nullptr, nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
std::this_thread::sleep_for(16ms);
}
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
// Now we read the segments
std::vector<OccupancyTracker::Segment> history;
@@ -1107,8 +1137,7 @@ TEST_F(BufferQueueTest, TestDiscardFreeBuffers) {
// Acquire and free 1 buffer
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
int releasedSlot = item.mSlot;
// Acquire 1 buffer, leaving 1 filled buffer in queue
@@ -1375,8 +1404,7 @@ TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) {
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
ASSERT_EQ(slot, item.mSlot);
ASSERT_NE(nullptr, item.mGraphicBuffer.get());
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
// Dequeue and queue the buffer again
ASSERT_EQ(OK,
@@ -1389,8 +1417,7 @@ TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) {
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
ASSERT_EQ(slot, item.mSlot);
ASSERT_EQ(nullptr, item.mGraphicBuffer.get());
- ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
// Dequeue and queue the buffer again
ASSERT_EQ(OK,
@@ -1569,4 +1596,278 @@ TEST_F(BufferQueueTest, TestAdditionalOptions) {
EXPECT_EQ(ADATASPACE_UNKNOWN, dataSpace);
}
+TEST_F(BufferQueueTest, PassesThroughPictureProfileHandle) {
+ createBufferQueue();
+ sp<MockConsumer> mc(new MockConsumer);
+ mConsumer->consumerConnect(mc, false);
+
+ IGraphicBufferProducer::QueueBufferOutput qbo;
+ mProducer->connect(new StubProducerListener, NATIVE_WINDOW_API_CPU, false, &qbo);
+ mProducer->setMaxDequeuedBufferCount(2);
+ mConsumer->setMaxAcquiredBufferCount(2);
+
+ // First try to pass a valid picture profile handle
+ {
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buf;
+ IGraphicBufferProducer::QueueBufferInput qbi(0, false, HAL_DATASPACE_UNKNOWN,
+ Rect(0, 0, 1, 1),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
+ Fence::NO_FENCE);
+ qbi.setPictureProfileHandle(PictureProfileHandle(1));
+
+ EXPECT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&slot, &fence, 1, 1, 0, GRALLOC_USAGE_SW_READ_OFTEN,
+ nullptr, nullptr));
+ EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buf));
+ EXPECT_EQ(OK, mProducer->queueBuffer(slot, qbi, &qbo));
+
+ BufferItem item;
+ EXPECT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+
+ ASSERT_TRUE(item.mPictureProfileHandle.has_value());
+ ASSERT_EQ(item.mPictureProfileHandle, PictureProfileHandle(1));
+ }
+
+ // Then validate that the picture profile handle isn't sticky and is reset for the next buffer
+ {
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buf;
+ IGraphicBufferProducer::QueueBufferInput qbi(0, false, HAL_DATASPACE_UNKNOWN,
+ Rect(0, 0, 1, 1),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
+ Fence::NO_FENCE);
+
+ EXPECT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&slot, &fence, 1, 1, 0, GRALLOC_USAGE_SW_READ_OFTEN,
+ nullptr, nullptr));
+ EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buf));
+ EXPECT_EQ(OK, mProducer->queueBuffer(slot, qbi, &qbo));
+
+ BufferItem item;
+ EXPECT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+
+ ASSERT_FALSE(item.mPictureProfileHandle.has_value());
+ }
+}
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+struct MockUnlimitedSlotConsumer : public MockConsumer {
+ virtual void onSlotCountChanged(int size) override { mSize = size; }
+
+ std::optional<int> mSize;
+};
+
+TEST_F(BufferQueueTest, UnlimitedSlots_FailsWhenNotAllowed) {
+ createBufferQueue();
+
+ sp<MockUnlimitedSlotConsumer> mc = sp<MockUnlimitedSlotConsumer>::make();
+ EXPECT_EQ(OK, mConsumer->consumerConnect(mc, false));
+
+ EXPECT_EQ(INVALID_OPERATION, mProducer->extendSlotCount(64));
+ EXPECT_EQ(INVALID_OPERATION, mProducer->extendSlotCount(32));
+ EXPECT_EQ(INVALID_OPERATION, mProducer->extendSlotCount(128));
+
+ EXPECT_EQ(std::nullopt, mc->mSize);
+}
+
+TEST_F(BufferQueueTest, UnlimitedSlots_OnlyAllowedForExtensions) {
+ createBufferQueue();
+
+ sp<MockUnlimitedSlotConsumer> consumerListener = sp<MockUnlimitedSlotConsumer>::make();
+ EXPECT_EQ(OK, mConsumer->consumerConnect(consumerListener, false));
+ EXPECT_EQ(OK, mConsumer->allowUnlimitedSlots(true));
+
+ EXPECT_EQ(BAD_VALUE, mProducer->extendSlotCount(32));
+ EXPECT_EQ(OK, mProducer->extendSlotCount(64));
+ EXPECT_EQ(OK, mProducer->extendSlotCount(128));
+ EXPECT_EQ(128, *consumerListener->mSize);
+
+ EXPECT_EQ(OK, mProducer->extendSlotCount(128));
+ EXPECT_EQ(BAD_VALUE, mProducer->extendSlotCount(127));
+}
+
+class BufferQueueUnlimitedTest : public BufferQueueTest {
+protected:
+ static constexpr auto kMaxBufferCount = 128;
+ static constexpr auto kAcquirableBufferCount = 2;
+ static constexpr auto kDequeableBufferCount = kMaxBufferCount - kAcquirableBufferCount;
+
+ virtual void SetUp() override {
+ BufferQueueTest::SetUp();
+
+ createBufferQueue();
+ setUpConsumer();
+ setUpProducer();
+ }
+
+ void setUpConsumer() {
+ EXPECT_EQ(OK, mConsumer->consumerConnect(mConsumerListener, false));
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ EXPECT_EQ(OK, mConsumer->allowUnlimitedSlots(true));
+#endif
+ EXPECT_EQ(OK, mConsumer->setConsumerUsageBits(GraphicBuffer::USAGE_SW_READ_OFTEN));
+ EXPECT_EQ(OK, mConsumer->setDefaultBufferSize(10, 10));
+ EXPECT_EQ(OK, mConsumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+ EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(kAcquirableBufferCount));
+ }
+
+ void setUpProducer() {
+ EXPECT_EQ(OK, mProducer->extendSlotCount(kMaxBufferCount));
+
+ IGraphicBufferProducer::QueueBufferOutput output;
+ EXPECT_EQ(OK,
+ mProducer->connect(mProducerListener, NATIVE_WINDOW_API_CPU,
+ /*producerControlledByApp*/ true, &output));
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+ ASSERT_TRUE(output.isSlotExpansionAllowed);
+#endif
+ ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(kDequeableBufferCount));
+ ASSERT_EQ(OK, mProducer->allowAllocation(true));
+ }
+
+ std::unordered_map<int, sp<Fence>> dequeueAll() {
+ std::unordered_map<int, sp<Fence>> slotsToFences;
+
+ for (int i = 0; i < kDequeableBufferCount; ++i) {
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buffer;
+
+ status_t ret =
+ mProducer->dequeueBuffer(&slot, &fence, /*w*/ 0, /*h*/ 0, /*format*/ 0,
+ /*uint64_t*/ 0,
+ /*outBufferAge*/ nullptr, /*outTimestamps*/ nullptr);
+ if (ret & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
+ EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buffer))
+ << "Unable to request buffer for slot " << slot;
+ }
+ EXPECT_FALSE(slotsToFences.contains(slot));
+ slotsToFences.emplace(slot, fence);
+ }
+ EXPECT_EQ(kDequeableBufferCount, (int)slotsToFences.size());
+ return slotsToFences;
+ }
+
+ sp<MockUnlimitedSlotConsumer> mConsumerListener = sp<MockUnlimitedSlotConsumer>::make();
+ sp<StubProducerListener> mProducerListener = sp<StubProducerListener>::make();
+};
+
+TEST_F(BufferQueueUnlimitedTest, ExpandOverridesConsumerMaxBuffers) {
+ createBufferQueue();
+ setUpConsumer();
+ EXPECT_EQ(OK, mConsumer->setMaxBufferCount(10));
+
+ setUpProducer();
+
+ EXPECT_EQ(kDequeableBufferCount, (int)dequeueAll().size());
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanDetachAll) {
+ auto slotsToFences = dequeueAll();
+ for (auto& [slot, fence] : slotsToFences) {
+ EXPECT_EQ(OK, mProducer->detachBuffer(slot));
+ }
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanCancelAll) {
+ auto slotsToFences = dequeueAll();
+ for (auto& [slot, fence] : slotsToFences) {
+ EXPECT_EQ(OK, mProducer->cancelBuffer(slot, fence));
+ }
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanAcquireAndReleaseAll) {
+ auto slotsToFences = dequeueAll();
+ for (auto& [slot, fence] : slotsToFences) {
+ IGraphicBufferProducer::QueueBufferInput input;
+ input.fence = fence;
+
+ IGraphicBufferProducer::QueueBufferOutput output;
+ EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+ BufferItem buffer;
+ EXPECT_EQ(OK, mConsumer->acquireBuffer(&buffer, 0));
+ EXPECT_EQ(OK,
+ mConsumer->releaseBuffer(buffer.mSlot, buffer.mFrameNumber, EGL_NO_DISPLAY,
+ EGL_NO_SYNC, buffer.mFence));
+ }
+}
+
+TEST_F(BufferQueueUnlimitedTest, CanAcquireAndDetachAll) {
+ auto slotsToFences = dequeueAll();
+ for (auto& [slot, fence] : slotsToFences) {
+ IGraphicBufferProducer::QueueBufferInput input;
+ input.fence = fence;
+
+ IGraphicBufferProducer::QueueBufferOutput output;
+ EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+ BufferItem buffer;
+ EXPECT_EQ(OK, mConsumer->acquireBuffer(&buffer, 0));
+ EXPECT_EQ(OK, mConsumer->detachBuffer(buffer.mSlot));
+ }
+}
+
+TEST_F(BufferQueueUnlimitedTest, GetReleasedBuffersExtended) {
+ // First, acquire and release all the buffers so the consumer "knows" about
+ // them
+ auto slotsToFences = dequeueAll();
+
+ std::vector<bool> releasedSlots;
+ EXPECT_EQ(OK, mConsumer->getReleasedBuffersExtended(&releasedSlots));
+ for (auto& [slot, _] : slotsToFences) {
+ EXPECT_TRUE(releasedSlots[slot])
+ << "Slots that haven't been acquired will show up as released.";
+ }
+ for (auto& [slot, fence] : slotsToFences) {
+ IGraphicBufferProducer::QueueBufferInput input;
+ input.fence = fence;
+
+ IGraphicBufferProducer::QueueBufferOutput output;
+ EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+ BufferItem buffer;
+ EXPECT_EQ(OK, mConsumer->acquireBuffer(&buffer, 0));
+ EXPECT_EQ(OK,
+ mConsumer->releaseBuffer(buffer.mSlot, buffer.mFrameNumber, EGL_NO_DISPLAY,
+ EGL_NO_SYNC_KHR, buffer.mFence));
+ }
+
+ EXPECT_EQ(OK, mConsumer->getReleasedBuffersExtended(&releasedSlots));
+ for (auto& [slot, _] : slotsToFences) {
+ EXPECT_FALSE(releasedSlots[slot])
+ << "Slots that have been acquired will show up as not released.";
+ }
+
+ // Then, alternatively cancel and detach (release) buffers. Only detached
+ // buffers should be returned by getReleasedBuffersExtended
+ slotsToFences = dequeueAll();
+ std::set<int> cancelledSlots;
+ std::set<int> detachedSlots;
+ bool cancel;
+ for (auto& [slot, fence] : slotsToFences) {
+ if (cancel) {
+ EXPECT_EQ(OK, mProducer->cancelBuffer(slot, fence));
+ cancelledSlots.insert(slot);
+ } else {
+ EXPECT_EQ(OK, mProducer->detachBuffer(slot));
+ detachedSlots.insert(slot);
+ }
+ cancel = !cancel;
+ }
+
+ EXPECT_EQ(OK, mConsumer->getReleasedBuffersExtended(&releasedSlots));
+ for (int slot : detachedSlots) {
+ EXPECT_TRUE(releasedSlots[slot]) << "Slots that are detached are released.";
+ }
+ for (int slot : cancelledSlots) {
+ EXPECT_FALSE(releasedSlots[slot])
+ << "Slots that are still held in the queue are not released.";
+ }
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
} // namespace android
diff --git a/libs/gui/tests/BufferReleaseChannel_test.cpp b/libs/gui/tests/BufferReleaseChannel_test.cpp
index 11d122b525..74f69e1ff0 100644
--- a/libs/gui/tests/BufferReleaseChannel_test.cpp
+++ b/libs/gui/tests/BufferReleaseChannel_test.cpp
@@ -29,11 +29,11 @@ namespace {
// Helper function to check if two file descriptors point to the same file.
bool is_same_file(int fd1, int fd2) {
- struct stat stat1;
+ struct stat stat1 {};
if (fstat(fd1, &stat1) != 0) {
return false;
}
- struct stat stat2;
+ struct stat stat2 {};
if (fstat(fd2, &stat2) != 0) {
return false;
}
@@ -42,7 +42,18 @@ bool is_same_file(int fd1, int fd2) {
} // namespace
-TEST(BufferReleaseChannelTest, MessageFlattenable) {
+class BufferReleaseChannelTest : public testing::Test {
+protected:
+ std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> mConsumer;
+ std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> mProducer;
+
+ void SetUp() override {
+ ASSERT_EQ(OK,
+ BufferReleaseChannel::open("BufferReleaseChannelTest"s, mConsumer, mProducer));
+ }
+};
+
+TEST_F(BufferReleaseChannelTest, MessageFlattenable) {
ReleaseCallbackId releaseCallbackId{1, 2};
sp<Fence> releaseFence = sp<Fence>::make(memfd_create("fake-fence-fd", 0));
uint32_t maxAcquiredBufferCount = 5;
@@ -92,31 +103,23 @@ TEST(BufferReleaseChannelTest, MessageFlattenable) {
// Verify that the BufferReleaseChannel consume returns WOULD_BLOCK when there's no message
// available.
-TEST(BufferReleaseChannelTest, ConsumerEndpointIsNonBlocking) {
- std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> consumer;
- std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> producer;
- ASSERT_EQ(OK, BufferReleaseChannel::open("test-channel"s, consumer, producer));
-
+TEST_F(BufferReleaseChannelTest, ConsumerEndpointIsNonBlocking) {
ReleaseCallbackId releaseCallbackId;
sp<Fence> releaseFence;
uint32_t maxAcquiredBufferCount;
ASSERT_EQ(WOULD_BLOCK,
- consumer->readReleaseFence(releaseCallbackId, releaseFence, maxAcquiredBufferCount));
+ mConsumer->readReleaseFence(releaseCallbackId, releaseFence, maxAcquiredBufferCount));
}
// Verify that we can write a message to the BufferReleaseChannel producer and read that message
// using the BufferReleaseChannel consumer.
-TEST(BufferReleaseChannelTest, ProduceAndConsume) {
- std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> consumer;
- std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> producer;
- ASSERT_EQ(OK, BufferReleaseChannel::open("test-channel"s, consumer, producer));
-
+TEST_F(BufferReleaseChannelTest, ProduceAndConsume) {
sp<Fence> fence = sp<Fence>::make(memfd_create("fake-fence-fd", 0));
for (uint64_t i = 0; i < 64; i++) {
ReleaseCallbackId producerId{i, i + 1};
uint32_t maxAcquiredBufferCount = i + 2;
- ASSERT_EQ(OK, producer->writeReleaseFence(producerId, fence, maxAcquiredBufferCount));
+ ASSERT_EQ(OK, mProducer->writeReleaseFence(producerId, fence, maxAcquiredBufferCount));
}
for (uint64_t i = 0; i < 64; i++) {
@@ -127,7 +130,7 @@ TEST(BufferReleaseChannelTest, ProduceAndConsume) {
sp<Fence> consumerFence;
uint32_t maxAcquiredBufferCount;
ASSERT_EQ(OK,
- consumer->readReleaseFence(consumerId, consumerFence, maxAcquiredBufferCount));
+ mConsumer->readReleaseFence(consumerId, consumerFence, maxAcquiredBufferCount));
ASSERT_EQ(expectedId, consumerId);
ASSERT_TRUE(is_same_file(fence->get(), consumerFence->get()));
@@ -135,4 +138,16 @@ TEST(BufferReleaseChannelTest, ProduceAndConsume) {
}
}
+// Verify that BufferReleaseChannel::ConsumerEndpoint's socket can't be written to.
+TEST_F(BufferReleaseChannelTest, ConsumerSocketReadOnly) {
+ uint64_t data = 0;
+ ASSERT_EQ(-1, write(mConsumer->getFd().get(), &data, sizeof(uint64_t)));
+ ASSERT_EQ(errno, EPIPE);
+}
+
+// Verify that BufferReleaseChannel::ProducerEndpoint's socket can't be read from.
+TEST_F(BufferReleaseChannelTest, ProducerSocketWriteOnly) {
+ ASSERT_EQ(0, read(mProducer->getFd().get(), nullptr, sizeof(uint64_t)));
+}
+
} // namespace android \ No newline at end of file
diff --git a/libs/gui/tests/Choreographer_test.cpp b/libs/gui/tests/Choreographer_test.cpp
index 8db48d2eb0..314dea62d4 100644
--- a/libs/gui/tests/Choreographer_test.cpp
+++ b/libs/gui/tests/Choreographer_test.cpp
@@ -50,7 +50,7 @@ static void vsyncCallback(const AChoreographerFrameCallbackData* callbackData, v
TEST_F(ChoreographerTest, InputCallbackBeforeAnimation) {
sp<Looper> looper = Looper::prepare(0);
- Choreographer* choreographer = Choreographer::getForThread();
+ sp<Choreographer> choreographer = Choreographer::getForThread();
VsyncCallback animationCb;
choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &animationCb, 0,
CALLBACK_ANIMATION);
@@ -83,4 +83,4 @@ TEST_F(ChoreographerTest, InputCallbackBeforeAnimation) {
animationCb.frameTime.count());
}
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp
index f4239cb69e..9476930de3 100644
--- a/libs/gui/tests/CpuConsumer_test.cpp
+++ b/libs/gui/tests/CpuConsumer_test.cpp
@@ -803,6 +803,27 @@ INSTANTIATE_TEST_CASE_P(Rgba8888Tests,
::testing::ValuesIn(rgba8888TestSets));
#endif
-
-
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+TEST(CpuConsumerSlotTest, UnlimitedSlots_AcquireReleaseAll) {
+ sp<CpuConsumer> cpuConsumer = sp<CpuConsumer>::make(3);
+ sp<Surface> surface = cpuConsumer->getSurface();
+ sp<SurfaceListener> listener = sp<StubSurfaceListener>::make();
+
+ ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, listener));
+ ASSERT_EQ(OK, surface->setMaxDequeuedBufferCount(256));
+
+ std::vector<Surface::BatchBuffer> buffers(256);
+ EXPECT_EQ(OK, surface->dequeueBuffers(&buffers));
+
+ for (auto& buffer : buffers) {
+ sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(buffer.buffer);
+ sp<Fence> fence = sp<Fence>::make(buffer.fenceFd);
+ EXPECT_EQ(OK, surface->queueBuffer(graphicBuffer, fence));
+
+ CpuConsumer::LockedBuffer nativeBuffer;
+ EXPECT_EQ(OK, cpuConsumer->lockNextBuffer(&nativeBuffer));
+ EXPECT_EQ(OK, cpuConsumer->unlockBuffer(nativeBuffer));
+ }
+}
+#endif
} // namespace android
diff --git a/libs/gui/tests/DisconnectWaiter.h b/libs/gui/tests/DisconnectWaiter.h
index 6e6915b299..3d5f63375d 100644
--- a/libs/gui/tests/DisconnectWaiter.h
+++ b/libs/gui/tests/DisconnectWaiter.h
@@ -29,7 +29,7 @@ namespace android {
// no way to forward the events. This DisconnectWaiter will not let the
// disconnect finish until finishDisconnect() is called. It will
// also block until a disconnect is called
-class DisconnectWaiter : public BnConsumerListener {
+class DisconnectWaiter : public IConsumerListener {
public:
DisconnectWaiter () :
mWaitForDisconnect(false),
diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp
index 29eeaa806f..791f471a1d 100644
--- a/libs/gui/tests/DisplayEventStructLayout_test.cpp
+++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp
@@ -36,15 +36,16 @@ TEST(DisplayEventStructLayoutTest, TestEventAlignment) {
CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameInterval, 8);
CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.preferredFrameTimelineIndex, 16);
CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelinesLength, 20);
- CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines, 24);
- CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].vsyncId, 24);
+ CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.numberQueuedBuffers, 24);
+ CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines, 32);
+ CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].vsyncId, 32);
CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].deadlineTimestamp,
- 32);
+ 40);
CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
- vsyncData.frameTimelines[0].expectedPresentationTime, 40);
+ vsyncData.frameTimelines[0].expectedPresentationTime, 48);
// Also test the offsets of the last frame timeline. A loop is not used because the non-const
// index cannot be used in static_assert.
- const int lastFrameTimelineOffset = /* Start of array */ 24 +
+ const int lastFrameTimelineOffset = /* Start of array */ 32 +
(VsyncEventData::kFrameTimelinesCapacity - 1) * /* Size of FrameTimeline */ 24;
CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesCapacity - 1].vsyncId,
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 7d0b512cb4..cf05fd4ba5 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -34,6 +34,8 @@
#include <binder/Parcel.h>
#include <binder/ProcessState.h>
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferConsumer.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
@@ -75,7 +77,7 @@ sp<IInputFlinger> getInputFlinger() {
if (input == nullptr) {
ALOGE("Failed to link to input service");
} else {
- ALOGE("Linked to input");
+ ALOGI("Linked to input");
}
return interface_cast<IInputFlinger>(input);
}
@@ -83,6 +85,7 @@ sp<IInputFlinger> getInputFlinger() {
// We use the top 10 layers as a way to haphazardly place ourselves above anything else.
static const int LAYER_BASE = INT32_MAX - 10;
static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 5s;
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
class SynchronousWindowInfosReportedListener : public gui::BnWindowInfosReportedListener {
public:
@@ -112,91 +115,81 @@ public:
mInputFlinger = getInputFlinger();
if (noInputChannel) {
- mInputInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, true);
+ mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, true);
} else {
android::os::InputChannelCore tempChannel;
android::binder::Status result =
- mInputFlinger->createInputChannel("testchannels", &tempChannel);
+ mInputFlinger->createInputChannel(sc->getName() + " channel", &tempChannel);
if (!result.isOk()) {
ADD_FAILURE() << "binder call to createInputChannel failed";
}
mClientChannel = InputChannel::create(std::move(tempChannel));
- mInputInfo.token = mClientChannel->getConnectionToken();
+ mInputInfo->editInfo()->token = mClientChannel->getConnectionToken();
mInputConsumer = new InputConsumer(mClientChannel);
}
- mInputInfo.name = "Test info";
- mInputInfo.dispatchingTimeout = 5s;
- mInputInfo.globalScaleFactor = 1.0;
- mInputInfo.touchableRegion.orSelf(Rect(0, 0, width, height));
+ mInputInfo->editInfo()->name = "Test info";
+ mInputInfo->editInfo()->dispatchingTimeout = 5s;
+ mInputInfo->editInfo()->globalScaleFactor = 1.0;
+ mInputInfo->editInfo()->touchableRegion.orSelf(Rect(0, 0, width, height));
InputApplicationInfo aInfo;
aInfo.token = new BBinder();
aInfo.name = "Test app info";
aInfo.dispatchingTimeoutMillis =
std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
- mInputInfo.applicationInfo = aInfo;
+ mInputInfo->editInfo()->applicationInfo = aInfo;
}
static std::unique_ptr<InputSurface> makeColorInputSurface(const sp<SurfaceComposerClient>& scc,
- int width, int height) {
+ int width, int height,
+ const std::string& name) {
sp<SurfaceControl> surfaceControl =
- scc->createSurface(String8("Test Surface"), 0 /* bufHeight */, 0 /* bufWidth */,
+ scc->createSurface(String8(name.c_str()), 0 /* bufHeight */, 0 /* bufWidth */,
PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceEffect);
return std::make_unique<InputSurface>(surfaceControl, width, height);
}
static std::unique_ptr<InputSurface> makeBufferInputSurface(
- const sp<SurfaceComposerClient>& scc, int width, int height) {
+ const sp<SurfaceComposerClient>& scc, int width, int height,
+ const std::string& name = "Test Buffer Surface") {
sp<SurfaceControl> surfaceControl =
- scc->createSurface(String8("Test Buffer Surface"), width, height,
- PIXEL_FORMAT_RGBA_8888, 0 /* flags */);
+ scc->createSurface(String8(name.c_str()), width, height, PIXEL_FORMAT_RGBA_8888,
+ 0 /* flags */);
return std::make_unique<InputSurface>(surfaceControl, width, height);
}
static std::unique_ptr<InputSurface> makeContainerInputSurface(
- const sp<SurfaceComposerClient>& scc, int width, int height) {
+ const sp<SurfaceComposerClient>& scc, int width, int height,
+ const std::string& name = "Test Container Surface") {
sp<SurfaceControl> surfaceControl =
- scc->createSurface(String8("Test Container Surface"), 0 /* bufHeight */,
- 0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888,
+ scc->createSurface(String8(name.c_str()), 0 /* bufHeight */, 0 /* bufWidth */,
+ PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceContainer);
return std::make_unique<InputSurface>(surfaceControl, width, height);
}
static std::unique_ptr<InputSurface> makeContainerInputSurfaceNoInputChannel(
- const sp<SurfaceComposerClient>& scc, int width, int height) {
+ const sp<SurfaceComposerClient>& scc, int width, int height,
+ const std::string& name = "Test Container Surface") {
sp<SurfaceControl> surfaceControl =
- scc->createSurface(String8("Test Container Surface"), 100 /* height */,
- 100 /* width */, PIXEL_FORMAT_RGBA_8888,
+ scc->createSurface(String8(name.c_str()), 100 /* height */, 100 /* width */,
+ PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceContainer);
return std::make_unique<InputSurface>(surfaceControl, width, height,
true /* noInputChannel */);
}
static std::unique_ptr<InputSurface> makeCursorInputSurface(
- const sp<SurfaceComposerClient>& scc, int width, int height) {
+ const sp<SurfaceComposerClient>& scc, int width, int height,
+ const std::string& name = "Test Cursor Surface") {
sp<SurfaceControl> surfaceControl =
- scc->createSurface(String8("Test Cursor Surface"), 0 /* bufHeight */,
- 0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eCursorWindow);
+ scc->createSurface(String8(name.c_str()), 0 /* bufHeight */, 0 /* bufWidth */,
+ PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eCursorWindow);
return std::make_unique<InputSurface>(surfaceControl, width, height);
}
- InputEvent* consumeEvent(std::chrono::milliseconds timeout = 3000ms) {
- mClientChannel->waitForMessage(timeout);
-
- InputEvent* ev;
- uint32_t seqId;
- status_t consumed = mInputConsumer->consume(&mInputEventFactory, true, -1, &seqId, &ev);
- if (consumed != OK) {
- return nullptr;
- }
- status_t status = mInputConsumer->sendFinishedSignal(seqId, true);
- EXPECT_EQ(OK, status) << "Could not send finished signal";
- return ev;
- }
-
void assertFocusChange(bool hasFocus) {
InputEvent* ev = consumeEvent();
ASSERT_NE(ev, nullptr);
@@ -211,8 +204,8 @@ public:
ASSERT_EQ(InputEventType::MOTION, ev->getType());
MotionEvent* mev = static_cast<MotionEvent*>(ev);
EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
- EXPECT_EQ(x, mev->getX(0));
- EXPECT_EQ(y, mev->getY(0));
+ EXPECT_NEAR(x, mev->getX(0), EPSILON);
+ EXPECT_NEAR(y, mev->getY(0), EPSILON);
EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
ev = consumeEvent();
@@ -294,7 +287,7 @@ public:
transactionBody) {
SurfaceComposerClient::Transaction t;
transactionBody(t, mSurfaceControl);
- t.apply(true);
+ t.apply(/*synchronously=*/true);
}
virtual void showAt(int x, int y, Rect crop = Rect(0, 0, 100, 100)) {
@@ -307,30 +300,46 @@ public:
t.setAlpha(mSurfaceControl, 1);
auto reportedListener = sp<SynchronousWindowInfosReportedListener>::make();
t.addWindowInfosReportedListener(reportedListener);
- t.apply();
+ t.apply(/*synchronously=*/true);
reportedListener->wait();
}
void requestFocus(ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT) {
SurfaceComposerClient::Transaction t;
FocusRequest request;
- request.token = mInputInfo.token;
- request.windowName = mInputInfo.name;
+ request.token = mInputInfo->getInfo()->token;
+ request.windowName = mInputInfo->getInfo()->name;
request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
request.displayId = displayId.val();
t.setFocusedWindow(request);
- t.apply(true);
+ t.apply(/*synchronously=*/true);
}
public:
+ // But should be private
+ sp<gui::WindowInfoHandle> mInputInfo = sp<gui::WindowInfoHandle>::make();
sp<SurfaceControl> mSurfaceControl;
+
+private:
std::shared_ptr<InputChannel> mClientChannel;
sp<IInputFlinger> mInputFlinger;
- WindowInfo mInputInfo;
-
PreallocatedInputEventFactory mInputEventFactory;
InputConsumer* mInputConsumer;
+
+ InputEvent* consumeEvent(std::chrono::milliseconds timeout = 3000ms) {
+ mClientChannel->waitForMessage(timeout);
+
+ InputEvent* ev;
+ uint32_t seqId;
+ status_t consumed = mInputConsumer->consume(&mInputEventFactory, true, -1, &seqId, &ev);
+ if (consumed != OK) {
+ return nullptr;
+ }
+ status_t status = mInputConsumer->sendFinishedSignal(seqId, true);
+ EXPECT_EQ(OK, status) << "Could not send finished signal";
+ return ev;
+ }
};
class BlastInputSurface : public InputSurface {
@@ -363,7 +372,7 @@ public:
transactionBody) override {
SurfaceComposerClient::Transaction t;
transactionBody(t, mParentSurfaceControl);
- t.apply(true);
+ t.apply(/*synchronously=*/true);
}
void showAt(int x, int y, Rect crop = Rect(0, 0, 100, 100)) override {
@@ -377,7 +386,7 @@ public:
t.setInputWindowInfo(mSurfaceControl, mInputInfo);
t.setCrop(mSurfaceControl, crop);
t.setAlpha(mSurfaceControl, 1);
- t.apply(true);
+ t.apply(/*synchronously=*/true);
}
private:
@@ -408,8 +417,9 @@ public:
void TearDown() { mComposerClient->dispose(); }
- std::unique_ptr<InputSurface> makeSurface(int width, int height) {
- return InputSurface::makeColorInputSurface(mComposerClient, width, height);
+ std::unique_ptr<InputSurface> makeSurface(int width, int height,
+ const std::string& name = "Test Surface") const {
+ return InputSurface::makeColorInputSurface(mComposerClient, width, height, name);
}
void postBuffer(const sp<SurfaceControl>& layer, int32_t w, int32_t h) {
@@ -417,7 +427,7 @@ public:
BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE;
sp<GraphicBuffer> buffer =
new GraphicBuffer(w, h, PIXEL_FORMAT_RGBA_8888, 1, usageFlags, "test");
- Transaction().setBuffer(layer, buffer).apply(true);
+ Transaction().setBuffer(layer, buffer).apply(/*synchronously=*/true);
usleep(mBufferPostDelay);
}
@@ -458,7 +468,7 @@ TEST_F(InputSurfacesTest, can_receive_input) {
injectTap(101, 101);
- EXPECT_NE(surface->consumeEvent(), nullptr);
+ surface->expectTap(1, 1);
}
/**
@@ -468,10 +478,10 @@ TEST_F(InputSurfacesTest, can_receive_input) {
* reverse order.
*/
TEST_F(InputSurfacesTest, input_respects_positioning) {
- std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Left Surface");
surface->showAt(100, 100);
- std::unique_ptr<InputSurface> surface2 = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> surface2 = makeSurface(100, 100, "Right Surface");
surface2->showAt(200, 200);
injectTap(201, 201);
@@ -491,8 +501,8 @@ TEST_F(InputSurfacesTest, input_respects_positioning) {
}
TEST_F(InputSurfacesTest, input_respects_layering) {
- std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> surface2 = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface 1");
+ std::unique_ptr<InputSurface> surface2 = makeSurface(100, 100, "Test Surface 2");
surface->showAt(10, 10);
surface2->showAt(10, 10);
@@ -517,11 +527,11 @@ TEST_F(InputSurfacesTest, input_respects_layering) {
// (such as shadows in dialogs). Inputs sent to the client are offset such that 0,0 is the start
// of the client content.
TEST_F(InputSurfacesTest, input_respects_surface_insets) {
- std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface");
+ std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface");
bgSurface->showAt(100, 100);
- fgSurface->mInputInfo.surfaceInset = 5;
+ fgSurface->mInputInfo->editInfo()->surfaceInset = 5;
fgSurface->showAt(100, 100);
injectTap(106, 106);
@@ -532,12 +542,12 @@ TEST_F(InputSurfacesTest, input_respects_surface_insets) {
}
TEST_F(InputSurfacesTest, input_respects_surface_insets_with_replaceTouchableRegionWithCrop) {
- std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface");
+ std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface");
bgSurface->showAt(100, 100);
- fgSurface->mInputInfo.surfaceInset = 5;
- fgSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+ fgSurface->mInputInfo->editInfo()->surfaceInset = 5;
+ fgSurface->mInputInfo->editInfo()->replaceTouchableRegionWithCrop = true;
fgSurface->showAt(100, 100);
injectTap(106, 106);
@@ -549,11 +559,11 @@ TEST_F(InputSurfacesTest, input_respects_surface_insets_with_replaceTouchableReg
// Ensure a surface whose insets are cropped, handles the touch offset correctly. ref:b/120413463
TEST_F(InputSurfacesTest, input_respects_cropped_surface_insets) {
- std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> childSurface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100, "Parent Surface");
+ std::unique_ptr<InputSurface> childSurface = makeSurface(100, 100, "Child Surface");
parentSurface->showAt(100, 100);
- childSurface->mInputInfo.surfaceInset = 10;
+ childSurface->mInputInfo->editInfo()->surfaceInset = 10;
childSurface->showAt(100, 100);
childSurface->doTransaction([&](auto& t, auto& sc) {
@@ -570,11 +580,11 @@ TEST_F(InputSurfacesTest, input_respects_cropped_surface_insets) {
// Ensure a surface whose insets are scaled, handles the touch offset correctly.
TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets) {
- std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface");
+ std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface");
bgSurface->showAt(100, 100);
- fgSurface->mInputInfo.surfaceInset = 5;
+ fgSurface->mInputInfo->editInfo()->surfaceInset = 5;
fgSurface->showAt(100, 100);
fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 4.0); });
@@ -588,12 +598,12 @@ TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets) {
}
TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets_overflow) {
- std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface");
+ std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface");
bgSurface->showAt(100, 100);
// In case we pass the very big inset without any checking.
- fgSurface->mInputInfo.surfaceInset = INT32_MAX;
+ fgSurface->mInputInfo->editInfo()->surfaceInset = INT32_MAX;
fgSurface->showAt(100, 100);
fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
@@ -606,28 +616,29 @@ TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets_overflow) {
TEST_F(InputSurfacesTest, touchable_region) {
std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
- surface->mInputInfo.touchableRegion.set(Rect{19, 29, 21, 31});
+ surface->mInputInfo->editInfo()->touchableRegion.set(Rect{19, 29, 21, 31});
surface->showAt(11, 22);
// A tap within the surface but outside the touchable region should not be sent to the surface.
injectTap(20, 30);
- EXPECT_EQ(surface->consumeEvent(/*timeout=*/200ms), nullptr);
+ surface->assertNoEvent();
injectTap(31, 52);
surface->expectTap(20, 30);
}
TEST_F(InputSurfacesTest, input_respects_touchable_region_offset_overflow) {
- std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface");
+ std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface");
bgSurface->showAt(100, 100);
// Set the touchable region to the values at the limit of its corresponding type.
// Since the surface is offset from the origin, the touchable region will be transformed into
// display space, which would trigger an overflow or an underflow. Ensure that we are protected
// against such a situation.
- fgSurface->mInputInfo.touchableRegion.orSelf(Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX});
+ fgSurface->mInputInfo->editInfo()->touchableRegion.orSelf(
+ Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX});
fgSurface->showAt(100, 100);
@@ -638,11 +649,12 @@ TEST_F(InputSurfacesTest, input_respects_touchable_region_offset_overflow) {
}
TEST_F(InputSurfacesTest, input_respects_scaled_touchable_region_overflow) {
- std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface");
+ std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface");
bgSurface->showAt(0, 0);
- fgSurface->mInputInfo.touchableRegion.orSelf(Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX});
+ fgSurface->mInputInfo->editInfo()->touchableRegion.orSelf(
+ Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX});
fgSurface->showAt(0, 0);
fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
@@ -703,8 +715,8 @@ TEST_F(InputSurfacesTest, input_respects_buffer_layer_alpha) {
}
TEST_F(InputSurfacesTest, input_ignores_color_layer_alpha) {
- std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100, "Background Surface");
+ std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100, "Foreground Surface");
bgSurface->showAt(10, 10);
fgSurface->showAt(10, 10);
@@ -812,7 +824,7 @@ TEST_F(InputSurfacesTest, rotate_surface_with_scale) {
TEST_F(InputSurfacesTest, rotate_surface_with_scale_and_insets) {
std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
- surface->mInputInfo.surfaceInset = 5;
+ surface->mInputInfo->editInfo()->surfaceInset = 5;
surface->showAt(100, 100);
surface->doTransaction([](auto& t, auto& sc) {
@@ -835,17 +847,19 @@ TEST_F(InputSurfacesTest, rotate_surface_with_scale_and_insets) {
}
TEST_F(InputSurfacesTest, touch_flag_obscured) {
- std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Touchable Surface");
surface->showAt(100, 100);
// Add non touchable window to fully cover touchable window. Window behind gets touch, but
// with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED
- std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
- nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- nonTouchableSurface->mInputInfo.ownerUid = gui::Uid{22222};
+ std::unique_ptr<InputSurface> nonTouchableSurface =
+ makeSurface(100, 100, "Non-Touchable Surface");
+ nonTouchableSurface->mInputInfo->editInfo()
+ ->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
+ nonTouchableSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222};
// Overriding occlusion mode otherwise the touch would be discarded at InputDispatcher by
// the default obscured/untrusted touch filter introduced in S.
- nonTouchableSurface->mInputInfo.touchOcclusionMode = TouchOcclusionMode::ALLOW;
+ nonTouchableSurface->mInputInfo->editInfo()->touchOcclusionMode = TouchOcclusionMode::ALLOW;
nonTouchableSurface->showAt(100, 100);
injectTap(190, 199);
@@ -853,18 +867,21 @@ TEST_F(InputSurfacesTest, touch_flag_obscured) {
}
TEST_F(InputSurfacesTest, touch_flag_partially_obscured_with_crop) {
- std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface");
surface->showAt(100, 100);
// Add non touchable window to cover touchable window, but parent is cropped to not cover area
// that will be tapped. Window behind gets touch, but with flag
// AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED
- std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
- nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- parentSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- nonTouchableSurface->mInputInfo.ownerUid = gui::Uid{22222};
- parentSurface->mInputInfo.ownerUid = gui::Uid{22222};
+ std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100, "Parent Surface");
+ std::unique_ptr<InputSurface> nonTouchableSurface =
+ makeSurface(100, 100, "Non-Touchable Surface");
+ nonTouchableSurface->mInputInfo->editInfo()
+ ->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
+ parentSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE,
+ true);
+ nonTouchableSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222};
+ parentSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222};
nonTouchableSurface->showAt(0, 0);
parentSurface->showAt(100, 100);
@@ -878,17 +895,20 @@ TEST_F(InputSurfacesTest, touch_flag_partially_obscured_with_crop) {
}
TEST_F(InputSurfacesTest, touch_not_obscured_with_crop) {
- std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface");
surface->showAt(100, 100);
// Add non touchable window to cover touchable window, but parent is cropped to avoid covering
// the touchable window. Window behind gets touch with no obscured flags.
- std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100);
- std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
- nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- parentSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- nonTouchableSurface->mInputInfo.ownerUid = gui::Uid{22222};
- parentSurface->mInputInfo.ownerUid = gui::Uid{22222};
+ std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100, "Parent Surface");
+ std::unique_ptr<InputSurface> nonTouchableSurface =
+ makeSurface(100, 100, "Non-Touchable Surface");
+ nonTouchableSurface->mInputInfo->editInfo()
+ ->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
+ parentSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE,
+ true);
+ nonTouchableSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222};
+ parentSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222};
nonTouchableSurface->showAt(0, 0);
parentSurface->showAt(50, 50);
@@ -906,8 +926,9 @@ TEST_F(InputSurfacesTest, touch_not_obscured_with_zero_sized_bql) {
std::unique_ptr<InputSurface> bufferSurface =
InputSurface::makeBufferInputSurface(mComposerClient, 0, 0);
- bufferSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- bufferSurface->mInputInfo.ownerUid = gui::Uid{22222};
+ bufferSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE,
+ true);
+ bufferSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222};
surface->showAt(10, 10);
bufferSurface->showAt(50, 50, Rect::EMPTY_RECT);
@@ -921,8 +942,9 @@ TEST_F(InputSurfacesTest, touch_not_obscured_with_zero_sized_blast) {
std::unique_ptr<BlastInputSurface> bufferSurface =
BlastInputSurface::makeBlastInputSurface(mComposerClient, 0, 0);
- bufferSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- bufferSurface->mInputInfo.ownerUid = gui::Uid{22222};
+ bufferSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE,
+ true);
+ bufferSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222};
surface->showAt(10, 10);
bufferSurface->showAt(50, 50, Rect::EMPTY_RECT);
@@ -964,14 +986,15 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_scaled_without_crop_window) {
}
TEST_F(InputSurfacesTest, strict_unobscured_input_obscured_window) {
- std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
- surface->mInputInfo.ownerUid = gui::Uid{11111};
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface");
+ surface->mInputInfo->editInfo()->ownerUid = gui::Uid{11111};
surface->doTransaction(
[&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
surface->showAt(100, 100);
- std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100);
- obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222};
+ std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100, "Obscuring Surface");
+ obscuringSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE,
+ true);
+ obscuringSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222};
obscuringSurface->showAt(100, 100);
injectTap(101, 101);
surface->assertNoEvent();
@@ -983,14 +1006,15 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_obscured_window) {
}
TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) {
- std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
- surface->mInputInfo.ownerUid = gui::Uid{11111};
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface");
+ surface->mInputInfo->editInfo()->ownerUid = gui::Uid{11111};
surface->doTransaction(
[&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
surface->showAt(100, 100);
- std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100);
- obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222};
+ std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100, "Obscuring Surface");
+ obscuringSurface->mInputInfo->editInfo()->setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE,
+ true);
+ obscuringSurface->mInputInfo->editInfo()->ownerUid = gui::Uid{22222};
obscuringSurface->showAt(190, 190);
injectTap(101, 101);
@@ -1004,10 +1028,10 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) {
}
TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) {
- std::unique_ptr<InputSurface> parentSurface = makeSurface(300, 300);
+ std::unique_ptr<InputSurface> parentSurface = makeSurface(300, 300, "Parent Surface");
parentSurface->showAt(0, 0, Rect(0, 0, 300, 300));
- std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface");
surface->showAt(100, 100);
surface->doTransaction([&](auto& t, auto& sc) {
t.setDropInputMode(sc, gui::DropInputMode::OBSCURED);
@@ -1026,10 +1050,10 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) {
}
TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) {
- std::unique_ptr<InputSurface> parentSurface = makeSurface(300, 300);
+ std::unique_ptr<InputSurface> parentSurface = makeSurface(300, 300, "Parent Surface");
parentSurface->showAt(0, 0, Rect(0, 0, 300, 300));
- std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> surface = makeSurface(100, 100, "Test Surface");
surface->doTransaction([&](auto& t, auto& sc) {
t.setDropInputMode(sc, gui::DropInputMode::OBSCURED);
t.reparent(sc, parentSurface->mSurfaceControl);
@@ -1054,7 +1078,7 @@ TEST_F(InputSurfacesTest, ignore_touch_region_with_zero_sized_blast) {
BlastInputSurface::makeBlastInputSurface(mComposerClient, 0, 0);
surface->showAt(100, 100);
- bufferSurface->mInputInfo.touchableRegion.orSelf(Rect(0, 0, 200, 200));
+ bufferSurface->mInputInfo->editInfo()->touchableRegion.orSelf(Rect(0, 0, 200, 200));
bufferSurface->showAt(100, 100, Rect::EMPTY_RECT);
injectTap(101, 101);
@@ -1092,13 +1116,15 @@ TEST_F(InputSurfacesTest, layer_with_valid_crop_can_be_focused) {
*/
TEST_F(InputSurfacesTest, cropped_container_replaces_touchable_region_with_null_crop) {
std::unique_ptr<InputSurface> parentContainer =
- InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+ InputSurface::makeContainerInputSurface(mComposerClient, 0, 0,
+ "Parent Container Surface");
std::unique_ptr<InputSurface> containerSurface =
- InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+ InputSurface::makeContainerInputSurface(mComposerClient, 100, 100,
+ "Test Container Surface");
containerSurface->doTransaction(
[&](auto& t, auto& sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
- containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
- containerSurface->mInputInfo.touchableRegionCropHandle = nullptr;
+ containerSurface->mInputInfo->editInfo()->replaceTouchableRegionWithCrop = true;
+ containerSurface->mInputInfo->editInfo()->touchableRegionCropHandle = nullptr;
parentContainer->showAt(10, 10, Rect(0, 0, 20, 20));
containerSurface->showAt(10, 10, Rect(0, 0, 5, 5));
@@ -1116,14 +1142,22 @@ TEST_F(InputSurfacesTest, cropped_container_replaces_touchable_region_with_null_
* in its parent's touchable region. The input events should be in the layer's coordinate space.
*/
TEST_F(InputSurfacesTest, uncropped_container_replaces_touchable_region_with_null_crop) {
+ std::unique_ptr<InputSurface> bgContainer =
+ InputSurface::makeContainerInputSurface(mComposerClient, 0, 0,
+ "Background Container Surface");
std::unique_ptr<InputSurface> parentContainer =
- InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+ InputSurface::makeContainerInputSurface(mComposerClient, 0, 0,
+ "Parent Container Surface");
std::unique_ptr<InputSurface> containerSurface =
- InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+ InputSurface::makeContainerInputSurface(mComposerClient, 100, 100,
+ "Test Container Surface");
containerSurface->doTransaction(
[&](auto& t, auto& sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
- containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
- containerSurface->mInputInfo.touchableRegionCropHandle = nullptr;
+ containerSurface->mInputInfo->editInfo()->replaceTouchableRegionWithCrop = true;
+ containerSurface->mInputInfo->editInfo()->touchableRegionCropHandle = nullptr;
+ parentContainer->doTransaction(
+ [&](auto& t, auto& sc) { t.reparent(sc, bgContainer->mSurfaceControl); });
+ bgContainer->showAt(0, 0, Rect(0, 0, 100, 100));
parentContainer->showAt(10, 10, Rect(0, 0, 20, 20));
containerSurface->showAt(10, 10, Rect::INVALID_RECT);
@@ -1142,13 +1176,13 @@ TEST_F(InputSurfacesTest, uncropped_container_replaces_touchable_region_with_nul
*/
TEST_F(InputSurfacesTest, replace_touchable_region_with_crop) {
std::unique_ptr<InputSurface> cropLayer =
- InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+ InputSurface::makeContainerInputSurface(mComposerClient, 0, 0, "Crop Layer Surface");
cropLayer->showAt(50, 50, Rect(0, 0, 20, 20));
std::unique_ptr<InputSurface> containerSurface =
- InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
- containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
- containerSurface->mInputInfo.touchableRegionCropHandle =
+ InputSurface::makeContainerInputSurface(mComposerClient, 100, 100, "Container Surface");
+ containerSurface->mInputInfo->editInfo()->replaceTouchableRegionWithCrop = true;
+ containerSurface->mInputInfo->editInfo()->touchableRegionCropHandle =
cropLayer->mSurfaceControl->getHandle();
containerSurface->showAt(10, 10, Rect::INVALID_RECT);
@@ -1194,9 +1228,17 @@ public:
sp<IGraphicBufferConsumer> consumer;
sp<IGraphicBufferProducer> producer;
BufferQueue::createBufferQueue(&producer, &consumer);
- consumer->setConsumerName(String8("Virtual disp consumer"));
+ consumer->setConsumerName(String8("Virtual disp consumer (MultiDisplayTests)"));
consumer->setDefaultBufferSize(width, height);
- mProducers.push_back(producer);
+
+ class StubConsumerListener : public IConsumerListener {
+ virtual void onFrameAvailable(const BufferItem&) override {}
+ virtual void onBuffersReleased() override {}
+ virtual void onSidebandStreamChanged() override {}
+ };
+
+ consumer->consumerConnect(sp<StubConsumerListener>::make(), true);
+ mBufferQueues.push_back({consumer, producer});
std::string name = "VirtualDisplay";
name += std::to_string(mVirtualDisplays.size());
@@ -1207,13 +1249,13 @@ public:
t.setDisplayLayerStack(token, layerStack);
t.setDisplayProjection(token, ui::ROTATION_0, {0, 0, width, height},
{offsetX, offsetY, offsetX + width, offsetY + height});
- t.apply(true);
+ t.apply(/*synchronously=*/true);
mVirtualDisplays.push_back(token);
}
std::vector<sp<IBinder>> mVirtualDisplays;
- std::vector<sp<IGraphicBufferProducer>> mProducers;
+ std::vector<std::tuple<sp<IGraphicBufferConsumer>, sp<IGraphicBufferProducer>>> mBufferQueues;
};
TEST_F(MultiDisplayTests, drop_touch_if_layer_on_invalid_display) {
diff --git a/libs/gui/tests/FillBuffer.cpp b/libs/gui/tests/FillBuffer.cpp
index b60995a624..11383d906b 100644
--- a/libs/gui/tests/FillBuffer.cpp
+++ b/libs/gui/tests/FillBuffer.cpp
@@ -76,7 +76,7 @@ void fillYV12BufferRect(uint8_t* buf, int w, int h, int stride,
}
void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) {
- const size_t PIXEL_SIZE = 4;
+ constexpr size_t PIXEL_SIZE = 4;
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
off_t offset = (y * stride + x) * PIXEL_SIZE;
@@ -89,6 +89,21 @@ void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) {
}
}
+void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride, uint8_t r, uint8_t g, uint8_t b,
+ uint8_t a) {
+ constexpr size_t PIXEL_SIZE = 4;
+
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ off_t offset = (y * stride + x) * PIXEL_SIZE;
+ buf[offset] = r;
+ buf[offset + 1] = g;
+ buf[offset + 2] = b;
+ buf[offset + 3] = a;
+ }
+ }
+}
+
void produceOneRGBA8Frame(const sp<ANativeWindow>& anw) {
android_native_buffer_t* anb;
ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
diff --git a/libs/gui/tests/FillBuffer.h b/libs/gui/tests/FillBuffer.h
index b584179318..f5d6b8bbda 100644
--- a/libs/gui/tests/FillBuffer.h
+++ b/libs/gui/tests/FillBuffer.h
@@ -30,6 +30,8 @@ void fillYV12BufferRect(uint8_t* buf, int w, int h, int stride,
const android_native_rect_t& rect);
void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride);
+void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride, uint8_t r, uint8_t g, uint8_t b,
+ uint8_t a);
// Produce a single RGBA8 frame by filling a buffer with a checkerboard pattern
// using the CPU. This assumes that the ANativeWindow is already configured to
diff --git a/libs/gui/tests/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp
index 04bfb28f65..c5025331e1 100644
--- a/libs/gui/tests/FrameRateUtilsTest.cpp
+++ b/libs/gui/tests/FrameRateUtilsTest.cpp
@@ -34,7 +34,7 @@ TEST(FrameRateUtilsTest, ValidateFrameRate) {
ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
- EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_GTE,
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST,
ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
// Privileged APIs.
@@ -62,7 +62,7 @@ TEST(FrameRateUtilsTest, ValidateFrameRate) {
// Invalid compatibility.
EXPECT_FALSE(
ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
- EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_FALSE(ValidateFrameRate(60.0f, 3, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
// Invalid change frame rate strategy.
if (flags::bq_setframerate()) {
diff --git a/libs/gui/tests/Malicious.cpp b/libs/gui/tests/Malicious.cpp
index 376420c42b..cc16383f39 100644
--- a/libs/gui/tests/Malicious.cpp
+++ b/libs/gui/tests/Malicious.cpp
@@ -129,7 +129,7 @@ private:
int32_t mExpectedSlot = 0;
};
-class FakeListener : public BnConsumerListener {
+class FakeListener : public IConsumerListener {
public:
void onFrameAvailable(const BufferItem&) override {}
void onBuffersReleased() override {}
diff --git a/libs/gui/tests/MockConsumer.h b/libs/gui/tests/MockConsumer.h
index 4a6c51c17d..2e8bc63fb7 100644
--- a/libs/gui/tests/MockConsumer.h
+++ b/libs/gui/tests/MockConsumer.h
@@ -18,7 +18,7 @@
namespace android {
-struct MockConsumer : public BnConsumerListener {
+struct MockConsumer : public IConsumerListener {
void onFrameAvailable(const BufferItem& /* item */) override {}
void onBuffersReleased() override {}
void onSidebandStreamChanged() override {}
diff --git a/libs/gui/tests/RegionSampling_test.cpp b/libs/gui/tests/RegionSampling_test.cpp
index a0d8c53385..c35efe28a3 100644
--- a/libs/gui/tests/RegionSampling_test.cpp
+++ b/libs/gui/tests/RegionSampling_test.cpp
@@ -40,7 +40,7 @@ struct ChoreographerSync {
std::unique_lock<decltype(mutex_)> lk(mutex_);
auto check_event = [](auto const& ev) -> bool {
- return ev.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
+ return ev.header.type == DisplayEventType::DISPLAY_EVENT_VSYNC;
};
DisplayEventReceiver::Event ev_;
int evs = receiver_.getEvents(&ev_, 1);
diff --git a/libs/gui/tests/SamplingDemo.cpp b/libs/gui/tests/SamplingDemo.cpp
index f98437b4f8..8fea689c91 100644
--- a/libs/gui/tests/SamplingDemo.cpp
+++ b/libs/gui/tests/SamplingDemo.cpp
@@ -46,7 +46,8 @@ public:
SurfaceComposerClient::Transaction{}
.setLayer(mButton, 0x7fffffff)
- .setCrop(mButton, {0, 0, width - 2 * BUTTON_PADDING, height - 2 * BUTTON_PADDING})
+ .setCrop(mButton,
+ Rect{0, 0, width - 2 * BUTTON_PADDING, height - 2 * BUTTON_PADDING})
.setPosition(mButton, samplingArea.left + BUTTON_PADDING,
samplingArea.top + BUTTON_PADDING)
.setColor(mButton, half3{1, 1, 1})
@@ -59,7 +60,8 @@ public:
SurfaceComposerClient::Transaction{}
.setLayer(mButtonBlend, 0x7ffffffe)
.setCrop(mButtonBlend,
- {0, 0, width - 2 * SAMPLE_AREA_PADDING, height - 2 * SAMPLE_AREA_PADDING})
+ Rect{0, 0, width - 2 * SAMPLE_AREA_PADDING,
+ height - 2 * SAMPLE_AREA_PADDING})
.setPosition(mButtonBlend, samplingArea.left + SAMPLE_AREA_PADDING,
samplingArea.top + SAMPLE_AREA_PADDING)
.setColor(mButtonBlend, half3{1, 1, 1})
@@ -75,7 +77,7 @@ public:
SurfaceComposerClient::Transaction{}
.setLayer(mSamplingArea, 0x7ffffffd)
- .setCrop(mSamplingArea, {0, 0, 100, 32})
+ .setCrop(mSamplingArea, Rect{0, 0, 100, 32})
.setPosition(mSamplingArea, 490, 1606)
.setColor(mSamplingArea, half3{0, 1, 0})
.setAlpha(mSamplingArea, 0.1)
diff --git a/libs/gui/tests/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp
index f34b03eade..9570a369bd 100644
--- a/libs/gui/tests/StreamSplitter_test.cpp
+++ b/libs/gui/tests/StreamSplitter_test.cpp
@@ -32,7 +32,7 @@ namespace android {
class StreamSplitterTest : public ::testing::Test {};
-struct FakeListener : public BnConsumerListener {
+struct FakeListener : public IConsumerListener {
virtual void onFrameAvailable(const BufferItem& /* item */) {}
virtual void onBuffersReleased() {}
virtual void onSidebandStreamChanged() {}
@@ -95,8 +95,7 @@ TEST_F(StreamSplitterTest, OneInputOneOutput) {
ASSERT_EQ(*dataOut, TEST_DATA);
ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
- ASSERT_EQ(OK, outputConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
- EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+ ASSERT_EQ(OK, outputConsumer->releaseBuffer(item.mSlot, item.mFrameNumber, Fence::NO_FENCE));
// This should succeed even with allocation disabled since it will have
// received the buffer back from the output BufferQueue
@@ -168,9 +167,9 @@ TEST_F(StreamSplitterTest, OneInputMultipleOutputs) {
ASSERT_EQ(*dataOut, TEST_DATA);
ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
- ASSERT_EQ(OK, outputConsumers[output]->releaseBuffer(item.mSlot,
- item.mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
- Fence::NO_FENCE));
+ ASSERT_EQ(OK,
+ outputConsumers[output]->releaseBuffer(item.mSlot, item.mFrameNumber,
+ Fence::NO_FENCE));
}
// This should succeed even with allocation disabled since it will have
diff --git a/libs/gui/tests/SurfaceTextureGL_test.cpp b/libs/gui/tests/SurfaceTextureGL_test.cpp
index 449533aa57..b22b85332c 100644
--- a/libs/gui/tests/SurfaceTextureGL_test.cpp
+++ b/libs/gui/tests/SurfaceTextureGL_test.cpp
@@ -17,6 +17,8 @@
#define LOG_TAG "SurfaceTextureGL_test"
//#define LOG_NDEBUG 0
+#include <gmock/gmock.h>
+
#include "SurfaceTextureGL.h"
#include "DisconnectWaiter.h"
@@ -735,4 +737,30 @@ TEST_F(SurfaceTextureGLTest, InvalidWidthOrHeightFails) {
ASSERT_NE(NO_ERROR, mST->updateTexImage());
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+TEST_F(SurfaceTextureGLTest, TestUnlimitedSlots) {
+ ASSERT_EQ(OK, mSTC->connect(NATIVE_WINDOW_API_CPU, sp<StubSurfaceListener>::make()));
+ ASSERT_EQ(OK, mSTC->setMaxDequeuedBufferCount(256));
+
+ std::vector<Surface::BatchBuffer> buffers(256);
+ ASSERT_EQ(OK, mSTC->dequeueBuffers(&buffers));
+ ASSERT_EQ(256u, buffers.size());
+ ASSERT_THAT(buffers, Each(Field(&Surface::BatchBuffer::buffer, ::testing::NotNull())));
+
+ for (size_t i = 0; i < buffers.size(); ++i) {
+ sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(buffers[i].buffer);
+ sp<Fence> fence = sp<Fence>::make(buffers[i].fenceFd);
+
+ void* buf;
+ ASSERT_EQ(OK, graphicBuffer->lock(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, &buf));
+ fillRGBA8Buffer((uint8_t*)buf, graphicBuffer->getWidth(), graphicBuffer->getHeight(),
+ graphicBuffer->getStride(), i, i, i, i);
+ graphicBuffer->unlock();
+
+ ASSERT_EQ(OK, mSTC->queueBuffer(graphicBuffer, fence));
+ ASSERT_EQ(OK, mST->updateTexImage());
+ checkPixel(0, 0, i, i, i, i);
+ }
+}
+#endif
} // namespace android
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 88893b64ba..9ef3c84ae7 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -14,13 +14,10 @@
* limitations under the License.
*/
-#include "gui/view/Surface.h"
-#include "Constants.h"
-#include "MockConsumer.h"
-
#include <gtest/gtest.h>
#include <SurfaceFlingerProperties.h>
+#include <android/gui/IActivePictureListener.h>
#include <android/gui/IDisplayEventConnection.h>
#include <android/gui/ISurfaceComposer.h>
#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
@@ -35,10 +32,13 @@
#include <gui/IConsumerListener.h>
#include <gui/IGraphicBufferConsumer.h>
#include <gui/IGraphicBufferProducer.h>
+#include <gui/IProducerListener.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/SyncScreenCaptureListener.h>
+#include <gui/view/Surface.h>
+#include <nativebase/nativebase.h>
#include <private/gui/ComposerService.h>
#include <private/gui/ComposerServiceAIDL.h>
#include <sys/types.h>
@@ -46,6 +46,7 @@
#include <ui/BufferQueueDefs.h>
#include <ui/DisplayMode.h>
#include <ui/GraphicBuffer.h>
+#include <ui/PixelFormat.h>
#include <ui/Rect.h>
#include <utils/Errors.h>
#include <utils/String8.h>
@@ -54,9 +55,12 @@
#include <cstddef>
#include <cstdint>
#include <future>
+#include <iterator>
#include <limits>
#include <thread>
+#include "Constants.h"
+#include "MockConsumer.h"
#include "testserver/TestServerClient.h"
namespace android {
@@ -201,9 +205,9 @@ protected:
releasedItems.resize(1+extraDiscardedBuffers);
for (size_t i = 0; i < releasedItems.size(); i++) {
ASSERT_EQ(NO_ERROR, consumer->acquireBuffer(&releasedItems[i], 0));
- ASSERT_EQ(NO_ERROR, consumer->releaseBuffer(releasedItems[i].mSlot,
- releasedItems[i].mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
- Fence::NO_FENCE));
+ ASSERT_EQ(NO_ERROR,
+ consumer->releaseBuffer(releasedItems[i].mSlot, releasedItems[i].mFrameNumber,
+ Fence::NO_FENCE));
}
int32_t expectedReleaseCb = (enableReleasedCb ? releasedItems.size() : 0);
if (hasSurfaceListener) {
@@ -290,9 +294,7 @@ TEST_F(SurfaceTest, LayerCountIsOne) {
TEST_F(SurfaceTest, QueryConsumerUsage) {
const int TEST_USAGE_FLAGS =
GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER;
- sp<BufferItemConsumer> c = new BufferItemConsumer(TEST_USAGE_FLAGS);
-
- sp<Surface> s = c->getSurface();
+ auto [c, s] = BufferItemConsumer::create(TEST_USAGE_FLAGS);
sp<ANativeWindow> anw(s);
int flags = -1;
@@ -599,7 +601,7 @@ TEST_F(SurfaceTest, TestGetLastDequeueStartTime) {
ASSERT_GE(after, lastDequeueTime);
}
-class FakeConsumer : public BnConsumerListener {
+class FakeConsumer : public IConsumerListener {
public:
void onFrameAvailable(const BufferItem& /*item*/) override {}
void onBuffersReleased() override {}
@@ -1015,6 +1017,19 @@ public:
return binder::Status::ok();
}
+ binder::Status addActivePictureListener(const sp<gui::IActivePictureListener>&) {
+ return binder::Status::ok();
+ }
+
+ binder::Status removeActivePictureListener(const sp<gui::IActivePictureListener>&) {
+ return binder::Status::ok();
+ }
+
+ binder::Status getMaxLayerPictureProfiles(const sp<IBinder>& /*display*/,
+ int32_t* /*outMaxProfiles*/) {
+ return binder::Status::ok();
+ }
+
protected:
IBinder* onAsBinder() override { return nullptr; }
@@ -2360,8 +2375,7 @@ TEST_F(SurfaceTest, QueueAcquireReleaseDequeue_CalledInStack_DoesNotDeadlock) {
sp<IGraphicBufferConsumer> bqConsumer;
BufferQueue::createBufferQueue(&bqProducer, &bqConsumer);
- sp<BufferItemConsumer> consumer = sp<BufferItemConsumer>::make(bqConsumer, 3);
- sp<Surface> surface = sp<Surface>::make(bqProducer);
+ auto [consumer, surface] = BufferItemConsumer::create(3);
sp<ImmediateReleaseConsumerListener> consumerListener =
sp<ImmediateReleaseConsumerListener>::make(consumer);
consumer->setFrameAvailableListener(consumerListener);
@@ -2519,4 +2533,128 @@ TEST_F(SurfaceTest, QueueBufferOutput_TracksReplacements_Plural) {
}
#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
+TEST_F(SurfaceTest, UnlimitedSlots_FailsOnIncompatibleConsumer) {
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+
+ sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+ EXPECT_EQ(OK, consumer->allowUnlimitedSlots(false));
+ EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+
+ sp<Surface> surface = sp<Surface>::make(producer);
+ sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+ EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+ EXPECT_NE(OK, surface->setMaxDequeuedBufferCount(128))
+ << "We shouldn't be able to set high max buffer counts if the consumer doesn't allow "
+ "it";
+}
+
+TEST_F(SurfaceTest, UnlimitedSlots_CanDequeueAndQueueMoreThanOldMaximum) {
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+
+ sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+ EXPECT_EQ(OK, consumer->allowUnlimitedSlots(true));
+ EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+ EXPECT_EQ(OK, consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+ EXPECT_EQ(OK, consumer->setConsumerUsageBits(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN));
+
+ sp<Surface> surface = sp<Surface>::make(producer);
+ sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+ EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+ EXPECT_EQ(OK, surface->setMaxDequeuedBufferCount(128))
+ << "If unlimited slots are allowed, we should be able increase the max dequeued buffer "
+ "count arbitrarily";
+
+ std::vector<std::tuple<sp<GraphicBuffer>, sp<Fence>, int>> buffers;
+ for (int i = 0; i < 128; i++) {
+ sp<GraphicBuffer> buffer;
+ sp<Fence> fence;
+ ASSERT_EQ(OK, surface->dequeueBuffer(&buffer, &fence)) << "Unable to dequeue buffer #" << i;
+ buffers.push_back({buffer, fence, i});
+ }
+
+ for (auto& [buffer, fence, idx] : buffers) {
+ ASSERT_EQ(OK, surface->queueBuffer(buffer, fence)) << "Unable to queue buffer #" << idx;
+ }
+}
+
+TEST_F(SurfaceTest, UnlimitedSlots_CanDequeueAndDetachMoreThanOldMaximum) {
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+
+ sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+ EXPECT_EQ(OK, consumer->allowUnlimitedSlots(true));
+ EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+ EXPECT_EQ(OK, consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+ EXPECT_EQ(OK, consumer->setConsumerUsageBits(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN));
+
+ sp<Surface> surface = sp<Surface>::make(producer);
+ sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+ EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+ EXPECT_EQ(OK, surface->setMaxDequeuedBufferCount(128))
+ << "If unlimited slots are allowed, we should be able increase the max dequeued buffer "
+ "count arbitrarily";
+
+ std::vector<std::tuple<sp<GraphicBuffer>, sp<Fence>, int>> buffers;
+ for (int i = 0; i < 128; i++) {
+ sp<GraphicBuffer> buffer;
+ sp<Fence> fence;
+ ASSERT_EQ(OK, surface->dequeueBuffer(&buffer, &fence)) << "Unable to dequeue buffer #" << i;
+ buffers.push_back({buffer, fence, i});
+ }
+
+ for (auto& [buffer, _, idx] : buffers) {
+ ASSERT_EQ(OK, surface->detachBuffer(buffer)) << "Unable to detach buffer #" << idx;
+ }
+}
+
+TEST_F(SurfaceTest, UnlimitedSlots_BatchOperations) {
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+
+ sp<IConsumerListener> consumerListener = sp<FakeConsumer>::make();
+
+ EXPECT_EQ(OK, consumer->allowUnlimitedSlots(true));
+ EXPECT_EQ(OK, consumer->consumerConnect(consumerListener, /* consumerListener */ true));
+ EXPECT_EQ(OK, consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888));
+ EXPECT_EQ(OK, consumer->setConsumerUsageBits(AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN));
+
+ sp<Surface> surface = sp<Surface>::make(producer);
+ sp<SurfaceListener> surfaceListener = sp<StubSurfaceListener>::make();
+ EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener));
+
+ EXPECT_EQ(OK, surface->setMaxDequeuedBufferCount(128))
+ << "If unlimited slots are allowed, we should be able increase the max dequeued buffer "
+ "count arbitrarily";
+
+ std::vector<Surface::BatchBuffer> buffers(128);
+ EXPECT_EQ(OK, surface->dequeueBuffers(&buffers));
+ EXPECT_EQ(128u, buffers.size());
+
+ std::vector<Surface::BatchQueuedBuffer> queuedBuffers;
+ std::transform(buffers.begin(), buffers.end(), std::back_inserter(queuedBuffers),
+ [](Surface::BatchBuffer& buffer) {
+ Surface::BatchQueuedBuffer out;
+ out.buffer = buffer.buffer;
+ out.fenceFd = buffer.fenceFd;
+ return out;
+ });
+
+ std::vector<SurfaceQueueBufferOutput> outputs;
+ EXPECT_EQ(OK, surface->queueBuffers(queuedBuffers, &outputs));
+ EXPECT_EQ(128u, outputs.size());
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_UNLIMITED_SLOTS)
} // namespace android
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index ce22082a9f..e3f9a07b34 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -40,7 +40,18 @@ TEST(WindowInfo, ParcellingWithoutToken) {
ASSERT_EQ(OK, i.writeToParcel(&p));
p.setDataPosition(0);
i2.readFromParcel(&p);
- ASSERT_TRUE(i2.token == nullptr);
+ ASSERT_EQ(i2.token, nullptr);
+}
+
+TEST(WindowInfo, ParcellingWithoutCloneTransform) {
+ WindowInfo i, i2;
+ i.cloneLayerStackTransform.reset();
+
+ Parcel p;
+ ASSERT_EQ(OK, i.writeToParcel(&p));
+ p.setDataPosition(0);
+ i2.readFromParcel(&p);
+ ASSERT_EQ(i2.cloneLayerStackTransform, std::nullopt);
}
TEST(WindowInfo, Parcelling) {
@@ -71,6 +82,8 @@ TEST(WindowInfo, Parcelling) {
i.applicationInfo.token = new BBinder();
i.applicationInfo.dispatchingTimeoutMillis = 0x12345678ABCD;
i.focusTransferTarget = new BBinder();
+ i.cloneLayerStackTransform = ui::Transform();
+ i.cloneLayerStackTransform->set({5, -1, 100, 4, 0, 40, 0, 0, 1});
Parcel p;
i.writeToParcel(&p);
@@ -100,6 +113,7 @@ TEST(WindowInfo, Parcelling) {
ASSERT_EQ(i.touchableRegionCropHandle, i2.touchableRegionCropHandle);
ASSERT_EQ(i.applicationInfo, i2.applicationInfo);
ASSERT_EQ(i.focusTransferTarget, i2.focusTransferTarget);
+ ASSERT_EQ(i.cloneLayerStackTransform, i2.cloneLayerStackTransform);
}
TEST(InputApplicationInfo, Parcelling) {
diff --git a/libs/gui/tests/benchmarks/Android.bp b/libs/gui/tests/benchmarks/Android.bp
new file mode 100644
index 0000000000..a728bef977
--- /dev/null
+++ b/libs/gui/tests/benchmarks/Android.bp
@@ -0,0 +1,27 @@
+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_benchmark {
+ name: "libgui_benchmarks",
+ srcs: [
+ "*.cpp",
+ ],
+ defaults: ["libgui-defaults"],
+ static_libs: [
+ "libgmock",
+ "libgtest",
+ ],
+ shared_libs: [
+ "libgui",
+ ],
+ header_libs: [
+ "libsurfaceflinger_mocks_headers",
+ "surfaceflinger_tests_common_headers",
+ ],
+}
diff --git a/libs/gui/tests/benchmarks/Transaction_benchmarks.cpp b/libs/gui/tests/benchmarks/Transaction_benchmarks.cpp
new file mode 100644
index 0000000000..0a5189538b
--- /dev/null
+++ b/libs/gui/tests/benchmarks/Transaction_benchmarks.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 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 <benchmark/benchmark.h>
+#include <cstddef>
+#include <optional>
+#include <vector>
+#include "binder/Parcel.h"
+#include "gui/SurfaceComposerClient.h"
+#include "gui/SurfaceControl.h"
+#include "log/log_main.h"
+
+namespace android {
+namespace {
+using android::hardware::graphics::common::V1_1::BufferUsage;
+
+std::vector<sp<SurfaceControl>> createSurfaceControl(const char* name, size_t num) {
+ sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
+ LOG_FATAL_IF(client->initCheck() != OK, "Could not init SurfaceComposerClient");
+ std::vector<sp<SurfaceControl>> surfaceControls;
+ for (size_t i = 0; i < num; i++) {
+ surfaceControls.push_back(
+ client->createSurface(String8(name), 0, 0, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState));
+ }
+ return surfaceControls;
+}
+
+void applyTransaction(benchmark::State& state) {
+ std::vector<sp<SurfaceControl>> surfaceControls = createSurfaceControl(__func__, 5 /* num */);
+ for (auto _ : state) {
+ SurfaceComposerClient::Transaction t;
+ for (auto& sc : surfaceControls) {
+ t.setCrop(sc, FloatRect{1, 2, 3, 4});
+ t.setAutoRefresh(sc, true);
+ t.hide(sc);
+ t.setAlpha(sc, 0.5);
+ t.setCornerRadius(sc, 0.8);
+ }
+ Parcel p;
+ t.writeToParcel(&p);
+ t.clear();
+ benchmark::DoNotOptimize(t);
+ }
+}
+BENCHMARK(applyTransaction);
+
+// Mimic a buffer transaction with callbacks
+void applyBufferTransaction(benchmark::State& state) {
+ std::vector<sp<SurfaceControl>> surfaceControls = createSurfaceControl(__func__, 5 /* num */);
+ std::vector<sp<GraphicBuffer>> buffers;
+ for (size_t i = 0; i < surfaceControls.size(); i++) {
+ int64_t usageFlags = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+ BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE;
+ buffers.emplace_back(
+ sp<GraphicBuffer>::make(5, 5, PIXEL_FORMAT_RGBA_8888, 1, usageFlags, "test"));
+ }
+
+ for (auto _ : state) {
+ SurfaceComposerClient::Transaction t;
+ int i = 0;
+ for (auto& sc : surfaceControls) {
+ std::function<void(const ReleaseCallbackId&, const sp<Fence>& /*releaseFence*/,
+ std::optional<uint32_t> currentMaxAcquiredBufferCount)>
+ releaseBufferCallback;
+ t.setBuffer(sc, buffers[i], std::nullopt, std::nullopt, 5, releaseBufferCallback);
+ }
+ Parcel p;
+ // proxy for applying the transaction
+ t.writeToParcel(&p);
+ t.clear();
+ benchmark::DoNotOptimize(t);
+ }
+}
+BENCHMARK(applyBufferTransaction);
+
+void mergeTransaction(benchmark::State& state) {
+ std::vector<sp<SurfaceControl>> surfaceControls = createSurfaceControl(__func__, 5 /* num */);
+ for (auto _ : state) {
+ SurfaceComposerClient::Transaction t1;
+ for (auto& sc : surfaceControls) {
+ t1.setCrop(sc, FloatRect{1, 2, 3, 4});
+ t1.setAutoRefresh(sc, true);
+ t1.hide(sc);
+ t1.setAlpha(sc, 0.5);
+ t1.setCornerRadius(sc, 0.8);
+ }
+
+ SurfaceComposerClient::Transaction t2;
+ for (auto& sc : surfaceControls) {
+ t2.hide(sc);
+ t2.setAlpha(sc, 0.5);
+ t2.setCornerRadius(sc, 0.8);
+ t2.setBackgroundBlurRadius(sc, 5);
+ }
+ t1.merge(std::move(t2));
+ benchmark::DoNotOptimize(t1);
+ }
+}
+BENCHMARK(mergeTransaction);
+
+void readTransactionFromParcel(benchmark::State& state) {
+ std::vector<sp<SurfaceControl>> surfaceControls = createSurfaceControl(__func__, 5 /* num */);
+ SurfaceComposerClient::Transaction t;
+ for (auto& sc : surfaceControls) {
+ t.setCrop(sc, FloatRect{1, 2, 3, 4});
+ t.setAutoRefresh(sc, true);
+ t.hide(sc);
+ t.setAlpha(sc, 0.5);
+ t.setCornerRadius(sc, 0.8);
+ }
+ Parcel p;
+ t.writeToParcel(&p);
+ t.clear();
+
+ for (auto _ : state) {
+ SurfaceComposerClient::Transaction t2;
+ t2.readFromParcel(&p);
+ p.setDataPosition(0);
+ benchmark::DoNotOptimize(t2);
+ }
+}
+BENCHMARK(readTransactionFromParcel);
+
+} // namespace
+} // namespace android
diff --git a/libs/gui/tests/benchmarks/main.cpp b/libs/gui/tests/benchmarks/main.cpp
new file mode 100644
index 0000000000..685c7c6a26
--- /dev/null
+++ b/libs/gui/tests/benchmarks/main.cpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2024 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 <benchmark/benchmark.h>
+BENCHMARK_MAIN();
diff --git a/libs/gui/tests/testserver/TestServer.cpp b/libs/gui/tests/testserver/TestServer.cpp
index cd8824e355..17d1b4abe9 100644
--- a/libs/gui/tests/testserver/TestServer.cpp
+++ b/libs/gui/tests/testserver/TestServer.cpp
@@ -45,7 +45,7 @@
namespace android {
namespace {
-class TestConsumerListener : public BnConsumerListener {
+class TestConsumerListener : public IConsumerListener {
virtual void onFrameAvailable(const BufferItem&) override {}
virtual void onBuffersReleased() override {}
virtual void onSidebandStreamChanged() override {}
diff --git a/libs/gui/view/Surface.cpp b/libs/gui/view/Surface.cpp
index 84c2a6ac71..2cf7081ede 100644
--- a/libs/gui/view/Surface.cpp
+++ b/libs/gui/view/Surface.cpp
@@ -121,6 +121,42 @@ String16 Surface::readMaybeEmptyString16(const Parcel* parcel) {
return str.value_or(String16());
}
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+Surface Surface::fromSurface(const sp<android::Surface>& surface) {
+ if (surface == nullptr) {
+ ALOGE("%s: Error: view::Surface::fromSurface failed due to null surface.", __FUNCTION__);
+ return Surface();
+ }
+ Surface s;
+ s.name = String16(surface->getConsumerName());
+ s.graphicBufferProducer = surface->getIGraphicBufferProducer();
+ s.surfaceControlHandle = surface->getSurfaceControlHandle();
+ return s;
+}
+
+sp<android::Surface> Surface::toSurface() const {
+ if (graphicBufferProducer == nullptr) return nullptr;
+ return new android::Surface(graphicBufferProducer, false, surfaceControlHandle);
+}
+
+status_t Surface::getUniqueId(uint64_t* out_id) const {
+ if (graphicBufferProducer == nullptr) {
+ ALOGE("android::viewSurface::getUniqueId() failed because it's not initialized.");
+ return UNEXPECTED_NULL;
+ }
+ status_t status = graphicBufferProducer->getUniqueId(out_id);
+ if (status != OK) {
+ ALOGE("android::viewSurface::getUniqueId() failed.");
+ return status;
+ }
+ return OK;
+}
+
+bool Surface::isEmpty() const {
+ return graphicBufferProducer == nullptr;
+}
+#endif
+
std::string Surface::toString() const {
std::stringstream out;
out << name;
diff --git a/libs/input/AccelerationCurve.cpp b/libs/input/AccelerationCurve.cpp
index 0a92a71596..1ed9794105 100644
--- a/libs/input/AccelerationCurve.cpp
+++ b/libs/input/AccelerationCurve.cpp
@@ -40,6 +40,18 @@ static_assert(kSegments.back().maxPointerSpeedMmPerS == std::numeric_limits<doub
constexpr std::array<double, 15> kSensitivityFactors = {1, 2, 4, 6, 7, 8, 9, 10,
11, 12, 13, 14, 16, 18, 20};
+// Calculates the base gain for a given pointer sensitivity value.
+//
+// The base gain is a scaling factor that is applied to the pointer movement.
+// Higher sensitivity values result in larger base gains, which in turn result
+// in faster pointer movements.
+//
+// The base gain is calculated using a linear mapping function that maps the
+// sensitivity range [-7, 7] to a base gain range [1.0, 3.5].
+double calculateBaseGain(int32_t sensitivity) {
+ return 1.0 + (sensitivity + 7) * (3.5 - 1.0) / (7 + 7);
+}
+
} // namespace
std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity(
@@ -60,4 +72,13 @@ std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivi
return output;
}
+std::vector<AccelerationCurveSegment> createFlatAccelerationCurve(int32_t sensitivity) {
+ LOG_ALWAYS_FATAL_IF(sensitivity < -7 || sensitivity > 7, "Invalid pointer sensitivity value");
+ std::vector<AccelerationCurveSegment> output = {
+ AccelerationCurveSegment{std::numeric_limits<double>::infinity(),
+ calculateBaseGain(sensitivity),
+ /* reciprocal = */ 0}};
+ return output;
+}
+
} // namespace android \ No newline at end of file
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 35d704a1b4..ff26184a33 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -27,9 +27,9 @@ filegroup {
name: "inputconstants_aidl",
srcs: [
"android/os/IInputConstants.aidl",
+ "android/os/InputConfig.aidl",
"android/os/InputEventInjectionResult.aidl",
"android/os/InputEventInjectionSync.aidl",
- "android/os/InputConfig.aidl",
"android/os/MotionEventFlag.aidl",
"android/os/PointerIconType.aidl",
],
@@ -85,56 +85,63 @@ rust_bindgen {
source_stem: "bindings",
bindgen_flags: [
- "--verbose",
- "--allowlist-var=AMOTION_EVENT_ACTION_CANCEL",
- "--allowlist-var=AMOTION_EVENT_ACTION_UP",
- "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
- "--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
- "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
- "--allowlist-var=MAX_POINTER_ID",
- "--allowlist-var=AINPUT_SOURCE_CLASS_NONE",
+ "--allowlist-var=AINPUT_KEYBOARD_TYPE_ALPHABETIC",
+ "--allowlist-var=AINPUT_KEYBOARD_TYPE_NONE",
+ "--allowlist-var=AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC",
+ "--allowlist-var=AINPUT_SOURCE_BLUETOOTH_STYLUS",
"--allowlist-var=AINPUT_SOURCE_CLASS_BUTTON",
- "--allowlist-var=AINPUT_SOURCE_CLASS_POINTER",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_JOYSTICK",
"--allowlist-var=AINPUT_SOURCE_CLASS_NAVIGATION",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_NONE",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_POINTER",
"--allowlist-var=AINPUT_SOURCE_CLASS_POSITION",
- "--allowlist-var=AINPUT_SOURCE_CLASS_JOYSTICK",
- "--allowlist-var=AINPUT_SOURCE_UNKNOWN",
- "--allowlist-var=AINPUT_SOURCE_KEYBOARD",
"--allowlist-var=AINPUT_SOURCE_DPAD",
"--allowlist-var=AINPUT_SOURCE_GAMEPAD",
- "--allowlist-var=AINPUT_SOURCE_TOUCHSCREEN",
+ "--allowlist-var=AINPUT_SOURCE_HDMI",
+ "--allowlist-var=AINPUT_SOURCE_JOYSTICK",
+ "--allowlist-var=AINPUT_SOURCE_KEYBOARD",
"--allowlist-var=AINPUT_SOURCE_MOUSE",
- "--allowlist-var=AINPUT_SOURCE_STYLUS",
- "--allowlist-var=AINPUT_SOURCE_BLUETOOTH_STYLUS",
- "--allowlist-var=AINPUT_SOURCE_TRACKBALL",
"--allowlist-var=AINPUT_SOURCE_MOUSE_RELATIVE",
+ "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER",
+ "--allowlist-var=AINPUT_SOURCE_SENSOR",
+ "--allowlist-var=AINPUT_SOURCE_STYLUS",
"--allowlist-var=AINPUT_SOURCE_TOUCHPAD",
+ "--allowlist-var=AINPUT_SOURCE_TOUCHSCREEN",
"--allowlist-var=AINPUT_SOURCE_TOUCH_NAVIGATION",
- "--allowlist-var=AINPUT_SOURCE_JOYSTICK",
- "--allowlist-var=AINPUT_SOURCE_HDMI",
- "--allowlist-var=AINPUT_SOURCE_SENSOR",
- "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER",
- "--allowlist-var=AINPUT_KEYBOARD_TYPE_NONE",
- "--allowlist-var=AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC",
- "--allowlist-var=AINPUT_KEYBOARD_TYPE_ALPHABETIC",
- "--allowlist-var=AMETA_NONE",
- "--allowlist-var=AMETA_ALT_ON",
+ "--allowlist-var=AINPUT_SOURCE_TRACKBALL",
+ "--allowlist-var=AINPUT_SOURCE_UNKNOWN",
"--allowlist-var=AMETA_ALT_LEFT_ON",
+ "--allowlist-var=AMETA_ALT_ON",
"--allowlist-var=AMETA_ALT_RIGHT_ON",
- "--allowlist-var=AMETA_SHIFT_ON",
- "--allowlist-var=AMETA_SHIFT_LEFT_ON",
- "--allowlist-var=AMETA_SHIFT_RIGHT_ON",
- "--allowlist-var=AMETA_SYM_ON",
- "--allowlist-var=AMETA_FUNCTION_ON",
- "--allowlist-var=AMETA_CTRL_ON",
+ "--allowlist-var=AMETA_CAPS_LOCK_ON",
"--allowlist-var=AMETA_CTRL_LEFT_ON",
+ "--allowlist-var=AMETA_CTRL_ON",
"--allowlist-var=AMETA_CTRL_RIGHT_ON",
- "--allowlist-var=AMETA_META_ON",
+ "--allowlist-var=AMETA_FUNCTION_ON",
"--allowlist-var=AMETA_META_LEFT_ON",
+ "--allowlist-var=AMETA_META_ON",
"--allowlist-var=AMETA_META_RIGHT_ON",
- "--allowlist-var=AMETA_CAPS_LOCK_ON",
+ "--allowlist-var=AMETA_NONE",
"--allowlist-var=AMETA_NUM_LOCK_ON",
"--allowlist-var=AMETA_SCROLL_LOCK_ON",
+ "--allowlist-var=AMETA_SHIFT_LEFT_ON",
+ "--allowlist-var=AMETA_SHIFT_ON",
+ "--allowlist-var=AMETA_SHIFT_RIGHT_ON",
+ "--allowlist-var=AMETA_SYM_ON",
+ "--allowlist-var=AMOTION_EVENT_ACTION_CANCEL",
+ "--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
+ "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
+ "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
+ "--allowlist-var=AMOTION_EVENT_ACTION_UP",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_BACK",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_FORWARD",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_PRIMARY",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_SECONDARY",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_PRIMARY",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_STYLUS_SECONDARY",
+ "--allowlist-var=AMOTION_EVENT_BUTTON_TERTIARY",
+ "--allowlist-var=MAX_POINTER_ID",
+ "--verbose",
],
static_libs: [
@@ -143,9 +150,9 @@ rust_bindgen {
],
shared_libs: ["libc++"],
header_libs: [
- "native_headers",
- "jni_headers",
"flatbuffer_headers",
+ "jni_headers",
+ "native_headers",
],
}
@@ -179,8 +186,8 @@ cc_library_static {
host_supported: true,
cflags: [
"-Wall",
- "-Wextra",
"-Werror",
+ "-Wextra",
],
srcs: [
"FromRustToCpp.cpp",
@@ -205,31 +212,34 @@ cc_library {
cpp_std: "c++20",
host_supported: true,
cflags: [
+ "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
"-Wall",
- "-Wextra",
"-Werror",
+ "-Wextra",
"-Wno-unused-parameter",
- "-Wthread-safety",
"-Wshadow",
"-Wshadow-field-in-constructor-modified",
"-Wshadow-uncaptured-local",
- "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
+ "-Wthread-safety",
],
srcs: [
"AccelerationCurve.cpp",
+ "CoordinateFilter.cpp",
"Input.cpp",
"InputConsumer.cpp",
"InputConsumerNoResampling.cpp",
"InputDevice.cpp",
"InputEventLabels.cpp",
+ "InputFlags.cpp",
"InputTransport.cpp",
"InputVerifier.cpp",
- "Keyboard.cpp",
"KeyCharacterMap.cpp",
- "KeyboardClassifier.cpp",
"KeyLayoutMap.cpp",
+ "Keyboard.cpp",
+ "KeyboardClassifier.cpp",
"MotionPredictor.cpp",
"MotionPredictorMetricsManager.cpp",
+ "OneEuroFilter.cpp",
"PrintTools.cpp",
"PropertyMap.cpp",
"Resampler.cpp",
@@ -260,13 +270,13 @@ cc_library {
shared_libs: [
"android.companion.virtualdevice.flags-aconfig-cc",
+ "libPlatformProperties",
"libaconfig_storage_read_api_cc",
"libbase",
"libbinder",
"libbinder_ndk",
"libcutils",
"liblog",
- "libPlatformProperties",
"libtinyxml2",
"libutils",
"libz", // needed by libkernelconfigs
@@ -285,15 +295,15 @@ cc_library {
static_libs: [
"inputconstants-cpp",
- "libui-types",
- "libtflite_static",
"libkernelconfigs",
+ "libtflite_static",
+ "libui-types",
],
whole_static_libs: [
"com.android.input.flags-aconfig-cc",
- "libinput_rust_ffi",
"iinputflinger_aidl_lib_static",
+ "libinput_rust_ffi",
],
export_static_lib_headers: [
@@ -308,8 +318,8 @@ cc_library {
target: {
android: {
required: [
- "motion_predictor_model_prebuilt",
"motion_predictor_model_config",
+ "motion_predictor_model_prebuilt",
],
static_libs: [
"libstatslog_libinput",
@@ -370,9 +380,9 @@ cc_defaults {
cpp_std: "c++20",
host_supported: true,
shared_libs: [
- "libutils",
"libbase",
"liblog",
+ "libutils",
],
}
diff --git a/libs/input/CoordinateFilter.cpp b/libs/input/CoordinateFilter.cpp
new file mode 100644
index 0000000000..a32685bd53
--- /dev/null
+++ b/libs/input/CoordinateFilter.cpp
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2024 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.
+ */
+
+#define LOG_TAG "CoordinateFilter"
+
+#include <input/CoordinateFilter.h>
+
+namespace android {
+
+CoordinateFilter::CoordinateFilter(float minCutoffFreq, float beta)
+ : mXFilter{minCutoffFreq, beta}, mYFilter{minCutoffFreq, beta} {}
+
+void CoordinateFilter::filter(std::chrono::nanoseconds timestamp, PointerCoords& coords) {
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, mXFilter.filter(timestamp, coords.getX()));
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, mYFilter.filter(timestamp, coords.getY()));
+}
+
+} // namespace android
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index a2bb3453fe..155ea000e3 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -284,6 +284,36 @@ bool isStylusEvent(uint32_t source, const std::vector<PointerProperties>& proper
return false;
}
+bool isStylusHoverEvent(uint32_t source, const std::vector<PointerProperties>& properties,
+ int32_t action) {
+ return isStylusEvent(source, properties) && isHoverAction(action);
+}
+
+bool isFromMouse(uint32_t source, ToolType toolType) {
+ return isFromSource(source, AINPUT_SOURCE_MOUSE) && toolType == ToolType::MOUSE;
+}
+
+bool isFromTouchpad(uint32_t source, ToolType toolType) {
+ return isFromSource(source, AINPUT_SOURCE_MOUSE) && toolType == ToolType::FINGER;
+}
+
+bool isFromDrawingTablet(uint32_t source, ToolType toolType) {
+ return isFromSource(source, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS) &&
+ isStylusToolType(toolType);
+}
+
+bool isHoverAction(int32_t action) {
+ return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+ action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
+}
+
+bool isMouseOrTouchpad(uint32_t sources) {
+ // Check if this is a mouse or touchpad, but not a drawing tablet.
+ return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
+ (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
+ !isFromSource(sources, AINPUT_SOURCE_STYLUS));
+}
+
VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) {
return {{VerifiedInputEvent::Type::KEY, event.getDeviceId(), event.getEventTime(),
event.getSource(), event.getDisplayId()},
@@ -685,11 +715,12 @@ void MotionEvent::setCursorPosition(float x, float y) {
const PointerCoords* MotionEvent::getRawPointerCoords(size_t pointerIndex) const {
if (CC_UNLIKELY(pointerIndex < 0 || pointerIndex >= getPointerCount())) {
- LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for " << *this;
+ LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for "
+ << safeDump();
}
const size_t position = getHistorySize() * getPointerCount() + pointerIndex;
if (CC_UNLIKELY(position < 0 || position >= mSamplePointerCoords.size())) {
- LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << *this;
+ LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << safeDump();
}
return &mSamplePointerCoords[position];
}
@@ -705,15 +736,16 @@ float MotionEvent::getAxisValue(int32_t axis, size_t pointerIndex) const {
const PointerCoords* MotionEvent::getHistoricalRawPointerCoords(
size_t pointerIndex, size_t historicalIndex) const {
if (CC_UNLIKELY(pointerIndex < 0 || pointerIndex >= getPointerCount())) {
- LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for " << *this;
+ LOG(FATAL) << __func__ << ": Invalid pointer index " << pointerIndex << " for "
+ << safeDump();
}
if (CC_UNLIKELY(historicalIndex < 0 || historicalIndex > getHistorySize())) {
LOG(FATAL) << __func__ << ": Invalid historical index " << historicalIndex << " for "
- << *this;
+ << safeDump();
}
const size_t position = historicalIndex * getPointerCount() + pointerIndex;
if (CC_UNLIKELY(position < 0 || position >= mSamplePointerCoords.size())) {
- LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << *this;
+ LOG(FATAL) << __func__ << ": Invalid array index " << position << " for " << safeDump();
}
return &mSamplePointerCoords[position];
}
@@ -1144,6 +1176,53 @@ bool MotionEvent::operator==(const android::MotionEvent& o) const {
// clang-format on
}
+std::string MotionEvent::safeDump() const {
+ std::stringstream out;
+ // Field names have the m prefix here to make it easy to distinguish safeDump output from
+ // operator<< output in logs.
+ out << "MotionEvent { mAction=" << MotionEvent::actionToString(mAction);
+ if (mActionButton != 0) {
+ out << ", mActionButton=" << mActionButton;
+ }
+ if (mButtonState != 0) {
+ out << ", mButtonState=" << mButtonState;
+ }
+ if (mClassification != MotionClassification::NONE) {
+ out << ", mClassification=" << motionClassificationToString(mClassification);
+ }
+ if (mMetaState != 0) {
+ out << ", mMetaState=" << mMetaState;
+ }
+ if (mFlags != 0) {
+ out << ", mFlags=0x" << std::hex << mFlags << std::dec;
+ }
+ if (mEdgeFlags != 0) {
+ out << ", mEdgeFlags=" << mEdgeFlags;
+ }
+ out << ", mDownTime=" << mDownTime;
+ out << ", mDeviceId=" << mDeviceId;
+ out << ", mSource=" << inputEventSourceToString(mSource);
+ out << ", mDisplayId=" << mDisplayId;
+ out << ", mEventId=0x" << std::hex << mId << std::dec;
+ // Since we're not assuming the data is at all valid, we also limit the number of items that
+ // might be printed from vectors, in case the vector's size field is corrupted.
+ out << ", mPointerProperties=(" << mPointerProperties.size() << ")[";
+ for (size_t i = 0; i < mPointerProperties.size() && i < MAX_POINTERS; i++) {
+ out << (i > 0 ? ", " : "") << mPointerProperties.at(i);
+ }
+ out << "], mSampleEventTimes=(" << mSampleEventTimes.size() << ")[";
+ for (size_t i = 0; i < mSampleEventTimes.size() && i < 256; i++) {
+ out << (i > 0 ? ", " : "") << mSampleEventTimes.at(i);
+ }
+ out << "], mSamplePointerCoords=(" << mSamplePointerCoords.size() << ")[";
+ for (size_t i = 0; i < mSamplePointerCoords.size() && i < MAX_POINTERS; i++) {
+ const PointerCoords& coords = mSamplePointerCoords.at(i);
+ out << (i > 0 ? ", " : "") << "(" << coords.getX() << ", " << coords.getY() << ")";
+ }
+ out << "] }";
+ return out.str();
+}
+
std::ostream& operator<<(std::ostream& out, const MotionEvent& event) {
out << "MotionEvent { action=" << MotionEvent::actionToString(event.getAction());
if (event.getActionButton() != 0) {
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index cdbc1869c3..6087461f6c 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -17,9 +17,8 @@
#define LOG_TAG "InputConsumerNoResampling"
#define ATRACE_TAG ATRACE_TAG_INPUT
-#include <chrono>
-
#include <inttypes.h>
+#include <set>
#include <android-base/logging.h>
#include <android-base/properties.h>
@@ -39,6 +38,8 @@ namespace {
using std::chrono::nanoseconds;
+using android::base::Result;
+
/**
* Log debug messages relating to the consumer end of the transport channel.
* Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
@@ -170,23 +171,23 @@ InputMessage createTimelineMessage(int32_t inputEventId, nsecs_t gpuCompletedTim
return msg;
}
-bool isPointerEvent(const MotionEvent& motionEvent) {
- return (motionEvent.getSource() & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
+std::ostream& operator<<(std::ostream& out, const InputMessage& msg) {
+ out << ftl::enum_string(msg.header.type);
+ return out;
}
-} // namespace
-using android::base::Result;
+} // namespace
// --- InputConsumerNoResampling ---
-InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
- sp<Looper> looper,
- InputConsumerCallbacks& callbacks,
- std::unique_ptr<Resampler> resampler)
+InputConsumerNoResampling::InputConsumerNoResampling(
+ const std::shared_ptr<InputChannel>& channel, sp<Looper> looper,
+ InputConsumerCallbacks& callbacks,
+ std::function<std::unique_ptr<Resampler>()> resamplerCreator)
: mChannel{channel},
mLooper{looper},
mCallbacks{callbacks},
- mResampler{std::move(resampler)},
+ mResamplerCreator{std::move(resamplerCreator)},
mFdEvents(0) {
LOG_ALWAYS_FATAL_IF(mLooper == nullptr);
mCallback = sp<LooperEventCallback>::make(
@@ -199,12 +200,31 @@ InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<Input
InputConsumerNoResampling::~InputConsumerNoResampling() {
ensureCalledOnLooperThread(__func__);
- consumeBatchedInputEvents(/*requestedFrameTime=*/std::nullopt);
+ // If there are any remaining unread batches, send an ack for them and don't deliver
+ // them to callbacks.
+ for (auto& [_, batches] : mBatches) {
+ while (!batches.empty()) {
+ finishInputEvent(batches.front().header.seq, /*handled=*/false);
+ batches.pop();
+ }
+ }
+
while (!mOutboundQueue.empty()) {
processOutboundEvents();
// This is our last chance to ack the events. If we don't ack them here, we will get an ANR,
// so keep trying to send the events as long as they are present in the queue.
}
+ // However, it is still up to the app to finish any events that have already been delivered
+ // to the callbacks. If we wanted to change that behaviour and auto-finish all unfinished events
+ // that were already sent to callbacks, we could potentially loop through "mConsumeTimes"
+ // instead. We can't use "mBatchedSequenceNumbers" for this purpose, because it only contains
+ // batchable (i.e., ACTION_MOVE) events that were sent to the callbacks.
+ const size_t unfinishedEvents = mConsumeTimes.size();
+ LOG_IF(INFO, unfinishedEvents != 0)
+ << getName() << " has " << unfinishedEvents << " unfinished event(s)";
+ // Remove the fd from epoll, so that Looper does not call 'handleReceiveCallback' anymore.
+ // This must be done at the end of the destructor; otherwise, some of the other functions may
+ // call 'setFdEvents' as a side-effect, thus adding the fd back to the epoll set of the looper.
setFdEvents(0);
}
@@ -259,6 +279,15 @@ void InputConsumerNoResampling::processOutboundEvents() {
return; // try again later
}
+ if (result == DEAD_OBJECT) {
+ // If there's no one to receive events in the channel, there's no point in sending them.
+ // Drop all outbound events.
+ LOG(INFO) << "Channel " << mChannel->getName() << " died. Dropping outbound event "
+ << outboundMsg;
+ mOutboundQueue.pop();
+ setFdEvents(0);
+ continue;
+ }
// Some other error. Give up
LOG(FATAL) << "Failed to send outbound event on channel '" << mChannel->getName()
<< "'. status=" << statusToString(result) << "(" << result << ")";
@@ -319,7 +348,6 @@ void InputConsumerNoResampling::setFdEvents(int events) {
}
void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messages) {
- // TODO(b/297226446) : add resampling
for (const InputMessage& msg : messages) {
if (msg.header.type == InputMessage::Type::MOTION) {
const int32_t action = msg.body.motion.action;
@@ -329,12 +357,32 @@ void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messa
action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
(isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+
+ const bool canResample = (mResamplerCreator != nullptr) &&
+ (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER));
+ if (canResample) {
+ if (action == AMOTION_EVENT_ACTION_DOWN) {
+ if (std::unique_ptr<Resampler> resampler = mResamplerCreator();
+ resampler != nullptr) {
+ const auto [_, inserted] =
+ mResamplers.insert(std::pair(deviceId, std::move(resampler)));
+ LOG_IF(WARNING, !inserted) << deviceId << "already exists in mResamplers";
+ }
+ }
+ }
+
if (batchableEvent) {
// add it to batch
mBatches[deviceId].emplace(msg);
} else {
// consume all pending batches for this device immediately
- consumeBatchedInputEvents(deviceId, /*requestedFrameTime=*/std::nullopt);
+ consumeBatchedInputEvents(deviceId, /*requestedFrameTime=*/
+ std::numeric_limits<nsecs_t>::max());
+ if (canResample &&
+ (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL)) {
+ LOG_IF(INFO, mResamplers.erase(deviceId) == 0)
+ << deviceId << "does not exist in mResamplers";
+ }
handleMessage(msg);
}
} else {
@@ -452,13 +500,22 @@ void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const {
}
std::pair<std::unique_ptr<MotionEvent>, std::optional<uint32_t>>
-InputConsumerNoResampling::createBatchedMotionEvent(const nsecs_t requestedFrameTime,
+InputConsumerNoResampling::createBatchedMotionEvent(const std::optional<nsecs_t> requestedFrameTime,
std::queue<InputMessage>& messages) {
std::unique_ptr<MotionEvent> motionEvent;
std::optional<uint32_t> firstSeqForBatch;
- const nanoseconds resampleLatency =
- (mResampler != nullptr) ? mResampler->getResampleLatency() : nanoseconds{0};
- const nanoseconds adjustedFrameTime = nanoseconds{requestedFrameTime} - resampleLatency;
+
+ LOG_IF(FATAL, messages.empty()) << "messages queue is empty!";
+ const DeviceId deviceId = messages.front().body.motion.deviceId;
+ const auto resampler = mResamplers.find(deviceId);
+ const nanoseconds resampleLatency = (resampler != mResamplers.cend())
+ ? resampler->second->getResampleLatency()
+ : nanoseconds{0};
+ // When batching is not enabled, we want to consume all events. That's equivalent to having an
+ // infinite requestedFrameTime.
+ const nanoseconds adjustedFrameTime = (requestedFrameTime.has_value())
+ ? (nanoseconds{*requestedFrameTime} - resampleLatency)
+ : nanoseconds{std::numeric_limits<nsecs_t>::max()};
while (!messages.empty() &&
(messages.front().body.motion.eventTime <= adjustedFrameTime.count())) {
@@ -474,31 +531,31 @@ InputConsumerNoResampling::createBatchedMotionEvent(const nsecs_t requestedFrame
}
messages.pop();
}
+
// Check if resampling should be performed.
- if (motionEvent != nullptr && isPointerEvent(*motionEvent) && mResampler != nullptr) {
- InputMessage* futureSample = nullptr;
- if (!messages.empty()) {
- futureSample = &messages.front();
- }
- mResampler->resampleMotionEvent(nanoseconds{requestedFrameTime}, *motionEvent,
- futureSample);
+ InputMessage* futureSample = nullptr;
+ if (!messages.empty()) {
+ futureSample = &messages.front();
+ }
+ if ((motionEvent != nullptr) && (resampler != mResamplers.cend()) &&
+ (requestedFrameTime.has_value())) {
+ resampler->second->resampleMotionEvent(nanoseconds{*requestedFrameTime}, *motionEvent,
+ futureSample);
}
+
return std::make_pair(std::move(motionEvent), firstSeqForBatch);
}
bool InputConsumerNoResampling::consumeBatchedInputEvents(
std::optional<DeviceId> deviceId, std::optional<nsecs_t> requestedFrameTime) {
ensureCalledOnLooperThread(__func__);
- // When batching is not enabled, we want to consume all events. That's equivalent to having an
- // infinite requestedFrameTime.
- requestedFrameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
bool producedEvents = false;
for (auto deviceIdIter = (deviceId.has_value()) ? (mBatches.find(*deviceId))
: (mBatches.begin());
deviceIdIter != mBatches.cend(); ++deviceIdIter) {
std::queue<InputMessage>& messages = deviceIdIter->second;
- auto [motion, firstSeqForBatch] = createBatchedMotionEvent(*requestedFrameTime, messages);
+ auto [motion, firstSeqForBatch] = createBatchedMotionEvent(requestedFrameTime, messages);
if (motion != nullptr) {
LOG_ALWAYS_FATAL_IF(!firstSeqForBatch.has_value());
mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch);
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index c9030312f9..4a6f66e058 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -191,7 +191,9 @@ InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other)
mKeyboardLayoutInfo(other.mKeyboardLayoutInfo),
mSources(other.mSources),
mKeyboardType(other.mKeyboardType),
- mKeyCharacterMap(other.mKeyCharacterMap),
+ mKeyCharacterMap(other.mKeyCharacterMap
+ ? std::make_unique<KeyCharacterMap>(*other.mKeyCharacterMap)
+ : nullptr),
mUsiVersion(other.mUsiVersion),
mAssociatedDisplayId(other.mAssociatedDisplayId),
mEnabled(other.mEnabled),
@@ -204,6 +206,34 @@ InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other)
mLights(other.mLights),
mViewBehavior(other.mViewBehavior) {}
+InputDeviceInfo& InputDeviceInfo::operator=(const InputDeviceInfo& other) {
+ mId = other.mId;
+ mGeneration = other.mGeneration;
+ mControllerNumber = other.mControllerNumber;
+ mIdentifier = other.mIdentifier;
+ mAlias = other.mAlias;
+ mIsExternal = other.mIsExternal;
+ mHasMic = other.mHasMic;
+ mKeyboardLayoutInfo = other.mKeyboardLayoutInfo;
+ mSources = other.mSources;
+ mKeyboardType = other.mKeyboardType;
+ mKeyCharacterMap = other.mKeyCharacterMap
+ ? std::make_unique<KeyCharacterMap>(*other.mKeyCharacterMap)
+ : nullptr;
+ mUsiVersion = other.mUsiVersion;
+ mAssociatedDisplayId = other.mAssociatedDisplayId;
+ mEnabled = other.mEnabled;
+ mHasVibrator = other.mHasVibrator;
+ mHasBattery = other.mHasBattery;
+ mHasButtonUnderPad = other.mHasButtonUnderPad;
+ mHasSensor = other.mHasSensor;
+ mMotionRanges = other.mMotionRanges;
+ mSensors = other.mSensors;
+ mLights = other.mLights;
+ mViewBehavior = other.mViewBehavior;
+ return *this;
+}
+
InputDeviceInfo::~InputDeviceInfo() {
}
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index 8db0ca588b..b537feb68f 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -350,7 +350,26 @@ namespace android {
DEFINE_KEYCODE(MACRO_3), \
DEFINE_KEYCODE(MACRO_4), \
DEFINE_KEYCODE(EMOJI_PICKER), \
- DEFINE_KEYCODE(SCREENSHOT)
+ DEFINE_KEYCODE(SCREENSHOT), \
+ DEFINE_KEYCODE(DICTATE), \
+ DEFINE_KEYCODE(NEW), \
+ DEFINE_KEYCODE(CLOSE), \
+ DEFINE_KEYCODE(DO_NOT_DISTURB), \
+ DEFINE_KEYCODE(PRINT), \
+ DEFINE_KEYCODE(LOCK), \
+ DEFINE_KEYCODE(FULLSCREEN), \
+ DEFINE_KEYCODE(F13), \
+ DEFINE_KEYCODE(F14), \
+ DEFINE_KEYCODE(F15), \
+ DEFINE_KEYCODE(F16), \
+ DEFINE_KEYCODE(F17), \
+ DEFINE_KEYCODE(F18), \
+ DEFINE_KEYCODE(F19),\
+ DEFINE_KEYCODE(F20), \
+ DEFINE_KEYCODE(F21), \
+ DEFINE_KEYCODE(F22), \
+ DEFINE_KEYCODE(F23), \
+ DEFINE_KEYCODE(F24)
// NOTE: If you add a new axis here you must also add it to several other files.
// Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
diff --git a/libs/input/InputFlags.cpp b/libs/input/InputFlags.cpp
new file mode 100644
index 0000000000..f866f9b8f0
--- /dev/null
+++ b/libs/input/InputFlags.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2025 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 <input/InputFlags.h>
+
+#include <android-base/logging.h>
+#include <com_android_input_flags.h>
+#include <cutils/properties.h>
+
+#include <string>
+
+namespace android {
+
+bool InputFlags::connectedDisplaysCursorEnabled() {
+ static std::optional<bool> cachedDevOption;
+ if (!cachedDevOption.has_value()) {
+ char value[PROPERTY_VALUE_MAX];
+ constexpr static auto sysprop_name = "persist.wm.debug.desktop_experience_devopts";
+ const int devOptionEnabled =
+ property_get(sysprop_name, value, nullptr) > 0 ? std::atoi(value) : 0;
+ cachedDevOption = devOptionEnabled == 1;
+ }
+ if (cachedDevOption.value_or(false)) {
+ return true;
+ }
+ return com::android::input::flags::connected_displays_cursor();
+}
+
+bool InputFlags::connectedDisplaysCursorAndAssociatedDisplayCursorBugfixEnabled() {
+ return connectedDisplaysCursorEnabled() &&
+ com::android::input::flags::connected_displays_associated_display_cursor_bugfix();
+}
+
+} // namespace android \ No newline at end of file
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 77dcaa9ef2..d388d48e8d 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -327,8 +327,8 @@ std::unique_ptr<InputChannel> InputChannel::create(const std::string& name,
android::base::unique_fd fd, sp<IBinder> token) {
const int result = fcntl(fd, F_SETFL, O_NONBLOCK);
if (result != 0) {
- LOG_ALWAYS_FATAL("channel '%s' ~ Could not make socket non-blocking: %s", name.c_str(),
- strerror(errno));
+ LOG_ALWAYS_FATAL("channel '%s' ~ Could not make socket (%d) non-blocking: %s", name.c_str(),
+ fd.get(), strerror(errno));
return nullptr;
}
// using 'new' to access a non-public constructor
@@ -583,15 +583,6 @@ status_t InputPublisher::publishMotionEvent(
StringPrintf("publishMotionEvent(inputChannel=%s, action=%s)",
mChannel->getName().c_str(),
MotionEvent::actionToString(action).c_str()));
- if (verifyEvents()) {
- Result<void> result =
- mInputVerifier.processMovement(deviceId, source, action, pointerCount,
- pointerProperties, pointerCoords, flags);
- if (!result.ok()) {
- LOG(ERROR) << "Bad stream: " << result.error();
- return BAD_VALUE;
- }
- }
if (debugTransportPublisher()) {
std::string transformString;
transform.dump(transformString, "transform", " ");
@@ -657,8 +648,18 @@ status_t InputPublisher::publishMotionEvent(
msg.body.motion.pointers[i].properties = pointerProperties[i];
msg.body.motion.pointers[i].coords = pointerCoords[i];
}
+ const status_t status = mChannel->sendMessage(&msg);
- return mChannel->sendMessage(&msg);
+ if (status == OK && verifyEvents()) {
+ Result<void> result = mInputVerifier.processMovement(deviceId, source, action, actionButton,
+ pointerCount, pointerProperties,
+ pointerCoords, flags, buttonState);
+ if (!result.ok()) {
+ LOG(ERROR) << "Bad stream: " << result.error();
+ return BAD_VALUE;
+ }
+ }
+ return status;
}
status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool hasFocus) {
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
index cec244539e..7811acefd0 100644
--- a/libs/input/InputVerifier.cpp
+++ b/libs/input/InputVerifier.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "InputVerifier"
#include <android-base/logging.h>
+#include <com_android_input_flags.h>
#include <input/InputVerifier.h>
#include "input_cxx_bridge.rs.h"
@@ -26,17 +27,23 @@ using android::input::RustPointerProperties;
using DeviceId = int32_t;
+namespace input_flags = com::android::input::flags;
+
namespace android {
// --- InputVerifier ---
InputVerifier::InputVerifier(const std::string& name)
- : mVerifier(android::input::verifier::create(rust::String::lossy(name))){};
+ : mVerifier(
+ android::input::verifier::create(rust::String::lossy(name),
+ input_flags::enable_button_state_verification())) {
+}
Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, int32_t action,
- uint32_t pointerCount,
+ int32_t actionButton, uint32_t pointerCount,
const PointerProperties* pointerProperties,
- const PointerCoords* pointerCoords, int32_t flags) {
+ const PointerCoords* pointerCoords, int32_t flags,
+ int32_t buttonState) {
std::vector<RustPointerProperties> rpp;
for (size_t i = 0; i < pointerCount; i++) {
rpp.emplace_back(RustPointerProperties{.id = pointerProperties[i].id});
@@ -44,7 +51,9 @@ Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, i
rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
rust::String errorMessage =
android::input::verifier::process_movement(*mVerifier, deviceId, source, action,
- properties, static_cast<uint32_t>(flags));
+ actionButton, properties,
+ static_cast<uint32_t>(flags),
+ static_cast<uint32_t>(buttonState));
if (errorMessage.empty()) {
return {};
} else {
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index b0563abaf7..d2c49b113d 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -84,15 +84,15 @@ static String8 toString(const char16_t* chars, size_t numChars) {
KeyCharacterMap::KeyCharacterMap(const std::string& filename) : mLoadFileName(filename) {}
-base::Result<std::shared_ptr<KeyCharacterMap>> KeyCharacterMap::load(const std::string& filename,
+base::Result<std::unique_ptr<KeyCharacterMap>> KeyCharacterMap::load(const std::string& filename,
Format format) {
Tokenizer* tokenizer;
status_t status = Tokenizer::open(String8(filename.c_str()), &tokenizer);
if (status) {
return Errorf("Error {} opening key character map file {}.", status, filename.c_str());
}
- std::shared_ptr<KeyCharacterMap> map =
- std::shared_ptr<KeyCharacterMap>(new KeyCharacterMap(filename));
+ std::unique_ptr<KeyCharacterMap> map =
+ std::unique_ptr<KeyCharacterMap>(new KeyCharacterMap(filename));
if (!map.get()) {
ALOGE("Error allocating key character map.");
return Errorf("Error allocating key character map.");
@@ -365,6 +365,17 @@ int32_t KeyCharacterMap::applyKeyRemapping(int32_t fromKeyCode) const {
return toKeyCode;
}
+std::vector<int32_t> KeyCharacterMap::findKeyCodesMappedToKeyCode(int32_t toKeyCode) const {
+ std::vector<int32_t> fromKeyCodes;
+
+ for (const auto& [from, to] : mKeyRemapping) {
+ if (toKeyCode == to) {
+ fromKeyCodes.push_back(from);
+ }
+ }
+ return fromKeyCodes;
+}
+
std::pair<int32_t, int32_t> KeyCharacterMap::applyKeyBehavior(int32_t fromKeyCode,
int32_t fromMetaState) const {
int32_t toKeyCode = fromKeyCode;
@@ -604,7 +615,7 @@ std::unique_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel)
ALOGE("%s: Null parcel", __func__);
return nullptr;
}
- std::string loadFileName = parcel->readCString();
+ std::string loadFileName = parcel->readString8().c_str();
std::unique_ptr<KeyCharacterMap> map =
std::make_unique<KeyCharacterMap>(KeyCharacterMap(loadFileName));
map->mType = static_cast<KeyCharacterMap::KeyboardType>(parcel->readInt32());
@@ -693,7 +704,7 @@ void KeyCharacterMap::writeToParcel(Parcel* parcel) const {
ALOGE("%s: Null parcel", __func__);
return;
}
- parcel->writeCString(mLoadFileName.c_str());
+ parcel->writeString8(String8(mLoadFileName.c_str()));
parcel->writeInt32(static_cast<int32_t>(mType));
parcel->writeBool(mLayoutOverlayApplied);
diff --git a/libs/input/KeyboardClassifier.cpp b/libs/input/KeyboardClassifier.cpp
index 0c2c7be582..2a83919283 100644
--- a/libs/input/KeyboardClassifier.cpp
+++ b/libs/input/KeyboardClassifier.cpp
@@ -57,14 +57,14 @@ void KeyboardClassifier::notifyKeyboardChanged(DeviceId deviceId,
uint32_t deviceClasses) {
if (mRustClassifier) {
RustInputDeviceIdentifier rustIdentifier;
- rustIdentifier.name = identifier.name;
- rustIdentifier.location = identifier.location;
- rustIdentifier.unique_id = identifier.uniqueId;
+ rustIdentifier.name = rust::String::lossy(identifier.name);
+ rustIdentifier.location = rust::String::lossy(identifier.location);
+ rustIdentifier.unique_id = rust::String::lossy(identifier.uniqueId);
rustIdentifier.bus = identifier.bus;
rustIdentifier.vendor = identifier.vendor;
rustIdentifier.product = identifier.product;
rustIdentifier.version = identifier.version;
- rustIdentifier.descriptor = identifier.descriptor;
+ rustIdentifier.descriptor = rust::String::lossy(identifier.descriptor);
android::input::keyboardClassifier::notifyKeyboardChanged(**mRustClassifier, deviceId,
rustIdentifier, deviceClasses);
} else {
diff --git a/libs/input/OneEuroFilter.cpp b/libs/input/OneEuroFilter.cpp
new file mode 100644
index 0000000000..7b0d104da1
--- /dev/null
+++ b/libs/input/OneEuroFilter.cpp
@@ -0,0 +1,87 @@
+/**
+ * Copyright 2024 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.
+ */
+
+#define LOG_TAG "OneEuroFilter"
+
+#include <chrono>
+#include <cmath>
+
+#include <android-base/logging.h>
+#include <input/CoordinateFilter.h>
+
+namespace android {
+namespace {
+
+using namespace std::literals::chrono_literals;
+
+const float kHertzPerGigahertz = 1E9f;
+const float kGigahertzPerHertz = 1E-9f;
+
+// filteredSpeed's units are position per nanosecond. beta's units are 1 / position.
+inline float cutoffFreq(float minCutoffFreq, float beta, float filteredSpeed) {
+ return kHertzPerGigahertz *
+ ((minCutoffFreq * kGigahertzPerHertz) + beta * std::abs(filteredSpeed));
+}
+
+inline float smoothingFactor(std::chrono::nanoseconds samplingPeriod, float cutoffFreq) {
+ const float constant = 2.0f * M_PI * samplingPeriod.count() * (cutoffFreq * kGigahertzPerHertz);
+ return constant / (constant + 1);
+}
+
+inline float lowPassFilter(float rawValue, float prevFilteredValue, float smoothingFactor) {
+ return smoothingFactor * rawValue + (1 - smoothingFactor) * prevFilteredValue;
+}
+
+} // namespace
+
+OneEuroFilter::OneEuroFilter(float minCutoffFreq, float beta, float speedCutoffFreq)
+ : mMinCutoffFreq{minCutoffFreq}, mBeta{beta}, mSpeedCutoffFreq{speedCutoffFreq} {}
+
+float OneEuroFilter::filter(std::chrono::nanoseconds timestamp, float rawPosition) {
+ LOG_IF(FATAL, mPrevTimestamp.has_value() && (*mPrevTimestamp >= timestamp))
+ << "Timestamp must be greater than mPrevTimestamp. Timestamp: " << timestamp.count()
+ << "ns. mPrevTimestamp: " << mPrevTimestamp->count() << "ns";
+
+ const std::chrono::nanoseconds samplingPeriod =
+ (mPrevTimestamp.has_value()) ? (timestamp - *mPrevTimestamp) : 1s;
+
+ const float rawVelocity = (mPrevFilteredPosition.has_value())
+ ? ((rawPosition - *mPrevFilteredPosition) / (samplingPeriod.count()))
+ : 0.0f;
+
+ const float speedSmoothingFactor = smoothingFactor(samplingPeriod, mSpeedCutoffFreq);
+
+ const float filteredVelocity = (mPrevFilteredVelocity.has_value())
+ ? lowPassFilter(rawVelocity, *mPrevFilteredVelocity, speedSmoothingFactor)
+ : rawVelocity;
+
+ const float positionCutoffFreq = cutoffFreq(mMinCutoffFreq, mBeta, filteredVelocity);
+
+ const float positionSmoothingFactor = smoothingFactor(samplingPeriod, positionCutoffFreq);
+
+ const float filteredPosition = (mPrevFilteredPosition.has_value())
+ ? lowPassFilter(rawPosition, *mPrevFilteredPosition, positionSmoothingFactor)
+ : rawPosition;
+
+ mPrevTimestamp = timestamp;
+ mPrevRawPosition = rawPosition;
+ mPrevFilteredVelocity = filteredVelocity;
+ mPrevFilteredPosition = filteredPosition;
+
+ return filteredPosition;
+}
+
+} // namespace android
diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp
index 51fadf8ec1..3ab132d550 100644
--- a/libs/input/Resampler.cpp
+++ b/libs/input/Resampler.cpp
@@ -18,6 +18,8 @@
#include <algorithm>
#include <chrono>
+#include <iomanip>
+#include <ostream>
#include <android-base/logging.h>
#include <android-base/properties.h>
@@ -26,10 +28,7 @@
#include <input/Resampler.h>
#include <utils/Timers.h>
-using std::chrono::nanoseconds;
-
namespace android {
-
namespace {
const bool IS_DEBUGGABLE_BUILD =
@@ -39,6 +38,11 @@ const bool IS_DEBUGGABLE_BUILD =
true;
#endif
+/**
+ * Log debug messages about timestamp and coordinates of event resampling.
+ * Enable this via "adb shell setprop log.tag.LegacyResamplerResampling DEBUG"
+ * (requires restart)
+ */
bool debugResampling() {
if (!IS_DEBUGGABLE_BUILD) {
static const bool DEBUG_TRANSPORT_RESAMPLING =
@@ -49,6 +53,8 @@ bool debugResampling() {
return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
}
+using std::chrono::nanoseconds;
+
constexpr std::chrono::milliseconds RESAMPLE_LATENCY{5};
constexpr std::chrono::milliseconds RESAMPLE_MIN_DELTA{2};
@@ -75,6 +81,31 @@ PointerCoords calculateResampledCoords(const PointerCoords& a, const PointerCoor
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, lerp(a.getY(), b.getY(), alpha));
return resampledCoords;
}
+
+bool equalXY(const PointerCoords& a, const PointerCoords& b) {
+ return (a.getX() == b.getX()) && (a.getY() == b.getY());
+}
+
+void setMotionEventPointerCoords(MotionEvent& motionEvent, size_t sampleIndex, size_t pointerIndex,
+ const PointerCoords& pointerCoords) {
+ // Ideally, we should not cast away const. In this particular case, it's safe to cast away const
+ // and dereference getHistoricalRawPointerCoords returned pointer because motionEvent is a
+ // nonconst reference to a MotionEvent object, so mutating the object should not be undefined
+ // behavior; moreover, the invoked method guarantees to return a valid pointer. Otherwise, it
+ // fatally logs. Alternatively, we could've created a new MotionEvent from scratch, but this
+ // approach is simpler and more efficient.
+ PointerCoords& motionEventCoords = const_cast<PointerCoords&>(
+ *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex)));
+ motionEventCoords.setAxisValue(AMOTION_EVENT_AXIS_X, pointerCoords.getX());
+ motionEventCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, pointerCoords.getY());
+ motionEventCoords.isResampled = pointerCoords.isResampled;
+}
+
+std::ostream& operator<<(std::ostream& os, const PointerCoords& pointerCoords) {
+ os << "(" << pointerCoords.getX() << ", " << pointerCoords.getY() << ")";
+ return os;
+}
+
} // namespace
void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
@@ -82,49 +113,44 @@ void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
const size_t latestIndex = numSamples - 1;
const size_t secondToLatestIndex = (latestIndex > 0) ? (latestIndex - 1) : 0;
for (size_t sampleIndex = secondToLatestIndex; sampleIndex < numSamples; ++sampleIndex) {
- std::vector<Pointer> pointers;
- const size_t numPointers = motionEvent.getPointerCount();
- for (size_t pointerIndex = 0; pointerIndex < numPointers; ++pointerIndex) {
- // getSamplePointerCoords is the vector representation of a getHistorySize by
- // getPointerCount matrix.
- const PointerCoords& pointerCoords =
- motionEvent.getSamplePointerCoords()[sampleIndex * numPointers + pointerIndex];
- pointers.push_back(
- Pointer{*motionEvent.getPointerProperties(pointerIndex), pointerCoords});
+ PointerMap pointerMap;
+ for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
+ ++pointerIndex) {
+ pointerMap.insert(Pointer{*(motionEvent.getPointerProperties(pointerIndex)),
+ *(motionEvent.getHistoricalRawPointerCoords(pointerIndex,
+ sampleIndex))});
}
mLatestSamples.pushBack(
- Sample{nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}, pointers});
+ Sample{nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}, pointerMap});
}
}
LegacyResampler::Sample LegacyResampler::messageToSample(const InputMessage& message) {
- std::vector<Pointer> pointers;
+ PointerMap pointerMap;
for (uint32_t i = 0; i < message.body.motion.pointerCount; ++i) {
- pointers.push_back(Pointer{message.body.motion.pointers[i].properties,
- message.body.motion.pointers[i].coords});
+ pointerMap.insert(Pointer{message.body.motion.pointers[i].properties,
+ message.body.motion.pointers[i].coords});
}
- return Sample{nanoseconds{message.body.motion.eventTime}, pointers};
+ return Sample{nanoseconds{message.body.motion.eventTime}, pointerMap};
}
bool LegacyResampler::pointerPropertiesResampleable(const Sample& target, const Sample& auxiliary) {
- if (target.pointers.size() > auxiliary.pointers.size()) {
- LOG_IF(INFO, debugResampling())
- << "Not resampled. Auxiliary sample has fewer pointers than target sample.";
- return false;
- }
- for (size_t i = 0; i < target.pointers.size(); ++i) {
- if (target.pointers[i].properties.id != auxiliary.pointers[i].properties.id) {
- LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ID mismatch.";
+ for (const Pointer& pointer : target.pointerMap) {
+ const std::optional<Pointer> auxiliaryPointer =
+ auxiliary.pointerMap.find(PointerMap::PointerId{pointer.properties.id});
+ if (!auxiliaryPointer.has_value()) {
+ LOG_IF(INFO, debugResampling())
+ << "Not resampled. Auxiliary sample does not contain all pointers from target.";
return false;
}
- if (target.pointers[i].properties.toolType != auxiliary.pointers[i].properties.toolType) {
+ if (pointer.properties.toolType != auxiliaryPointer->properties.toolType) {
LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ToolType mismatch.";
return false;
}
- if (!canResampleTool(target.pointers[i].properties.toolType)) {
+ if (!canResampleTool(pointer.properties.toolType)) {
LOG_IF(INFO, debugResampling())
<< "Not resampled. Cannot resample "
- << ftl::enum_string(target.pointers[i].properties.toolType) << " ToolType.";
+ << ftl::enum_string(pointer.properties.toolType) << " ToolType.";
return false;
}
}
@@ -144,35 +170,40 @@ bool LegacyResampler::canInterpolate(const InputMessage& message) const {
const nanoseconds delta = futureSample.eventTime - pastSample.eventTime;
if (delta < RESAMPLE_MIN_DELTA) {
- LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
+ LOG_IF(INFO, debugResampling())
+ << "Not resampled. Delta is too small: " << std::setprecision(3)
+ << std::chrono::duration<double, std::milli>{delta}.count() << "ms";
return false;
}
return true;
}
std::optional<LegacyResampler::Sample> LegacyResampler::attemptInterpolation(
- nanoseconds resampleTime, const InputMessage& futureSample) const {
- if (!canInterpolate(futureSample)) {
+ nanoseconds resampleTime, const InputMessage& futureMessage) const {
+ if (!canInterpolate(futureMessage)) {
return std::nullopt;
}
LOG_IF(FATAL, mLatestSamples.empty())
<< "Not resampled. mLatestSamples must not be empty to interpolate.";
const Sample& pastSample = *(mLatestSamples.end() - 1);
+ const Sample& futureSample = messageToSample(futureMessage);
- const nanoseconds delta =
- nanoseconds{futureSample.body.motion.eventTime} - pastSample.eventTime;
+ const nanoseconds delta = nanoseconds{futureSample.eventTime} - pastSample.eventTime;
const float alpha =
- std::chrono::duration<float, std::milli>(resampleTime - pastSample.eventTime) / delta;
-
- std::vector<Pointer> resampledPointers;
- for (size_t i = 0; i < pastSample.pointers.size(); ++i) {
- const PointerCoords& resampledCoords =
- calculateResampledCoords(pastSample.pointers[i].coords,
- futureSample.body.motion.pointers[i].coords, alpha);
- resampledPointers.push_back(Pointer{pastSample.pointers[i].properties, resampledCoords});
+ std::chrono::duration<float, std::nano>(resampleTime - pastSample.eventTime) / delta;
+
+ PointerMap resampledPointerMap;
+ for (const Pointer& pointer : pastSample.pointerMap) {
+ if (std::optional<Pointer> futureSamplePointer =
+ futureSample.pointerMap.find(PointerMap::PointerId{pointer.properties.id});
+ futureSamplePointer.has_value()) {
+ const PointerCoords& resampledCoords =
+ calculateResampledCoords(pointer.coords, futureSamplePointer->coords, alpha);
+ resampledPointerMap.insert(Pointer{pointer.properties, resampledCoords});
+ }
}
- return Sample{resampleTime, resampledPointers};
+ return Sample{resampleTime, resampledPointerMap};
}
bool LegacyResampler::canExtrapolate() const {
@@ -190,10 +221,14 @@ bool LegacyResampler::canExtrapolate() const {
const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
if (delta < RESAMPLE_MIN_DELTA) {
- LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
+ LOG_IF(INFO, debugResampling())
+ << "Not resampled. Delta is too small: " << std::setprecision(3)
+ << std::chrono::duration<double, std::milli>{delta}.count() << "ms";
return false;
} else if (delta > RESAMPLE_MAX_DELTA) {
- LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too large: " << delta << "ns.";
+ LOG_IF(INFO, debugResampling())
+ << "Not resampled. Delta is too large: " << std::setprecision(3)
+ << std::chrono::duration<double, std::milli>{delta}.count() << "ms";
return false;
}
return true;
@@ -219,20 +254,28 @@ std::optional<LegacyResampler::Sample> LegacyResampler::attemptExtrapolation(
(resampleTime > farthestPrediction) ? (farthestPrediction) : (resampleTime);
LOG_IF(INFO, debugResampling() && newResampleTime == farthestPrediction)
<< "Resample time is too far in the future. Adjusting prediction from "
- << (resampleTime - presentSample.eventTime) << " to "
- << (farthestPrediction - presentSample.eventTime) << "ns.";
+ << std::setprecision(3)
+ << std::chrono::duration<double, std::milli>{resampleTime - presentSample.eventTime}
+ .count()
+ << "ms to "
+ << std::chrono::duration<double, std::milli>{farthestPrediction -
+ presentSample.eventTime}
+ .count()
+ << "ms";
const float alpha =
- std::chrono::duration<float, std::milli>(newResampleTime - pastSample.eventTime) /
- delta;
-
- std::vector<Pointer> resampledPointers;
- for (size_t i = 0; i < presentSample.pointers.size(); ++i) {
- const PointerCoords& resampledCoords =
- calculateResampledCoords(pastSample.pointers[i].coords,
- presentSample.pointers[i].coords, alpha);
- resampledPointers.push_back(Pointer{presentSample.pointers[i].properties, resampledCoords});
+ std::chrono::duration<float, std::nano>(newResampleTime - pastSample.eventTime) / delta;
+
+ PointerMap resampledPointerMap;
+ for (const Pointer& pointer : presentSample.pointerMap) {
+ if (std::optional<Pointer> pastSamplePointer =
+ pastSample.pointerMap.find(PointerMap::PointerId{pointer.properties.id});
+ pastSamplePointer.has_value()) {
+ const PointerCoords& resampledCoords =
+ calculateResampledCoords(pastSamplePointer->coords, pointer.coords, alpha);
+ resampledPointerMap.insert(Pointer{pointer.properties, resampledCoords});
+ }
}
- return Sample{newResampleTime, resampledPointers};
+ return Sample{newResampleTime, resampledPointerMap};
}
inline void LegacyResampler::addSampleToMotionEvent(const Sample& sample,
@@ -245,15 +288,87 @@ nanoseconds LegacyResampler::getResampleLatency() const {
return RESAMPLE_LATENCY;
}
-void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& motionEvent,
- const InputMessage* futureSample) {
- if (mPreviousDeviceId && *mPreviousDeviceId != motionEvent.getDeviceId()) {
- mLatestSamples.clear();
+/**
+ * The resampler is unaware of ACTION_DOWN. Thus, it needs to constantly check for pointer IDs
+ * occurrences. This problem could be fixed if the resampler has access to the entire stream of
+ * MotionEvent actions. That way, both ACTION_DOWN and ACTION_UP will be visible; therefore,
+ * facilitating pointer tracking between samples.
+ */
+void LegacyResampler::overwriteMotionEventSamples(MotionEvent& motionEvent) const {
+ const size_t numSamples = motionEvent.getHistorySize() + 1;
+ for (size_t sampleIndex = 0; sampleIndex < numSamples; ++sampleIndex) {
+ overwriteStillPointers(motionEvent, sampleIndex);
+ overwriteOldPointers(motionEvent, sampleIndex);
+ }
+}
+
+void LegacyResampler::overwriteStillPointers(MotionEvent& motionEvent, size_t sampleIndex) const {
+ if (!mLastRealSample.has_value() || !mPreviousPrediction.has_value()) {
+ LOG_IF(INFO, debugResampling()) << "Still pointers not overwritten. Not enough data.";
+ return;
+ }
+ for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); ++pointerIndex) {
+ const std::optional<Pointer> lastRealPointer = mLastRealSample->pointerMap.find(
+ PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)});
+ const std::optional<Pointer> previousPointer = mPreviousPrediction->pointerMap.find(
+ PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)});
+ // This could happen because resampler only receives ACTION_MOVE events.
+ if (!lastRealPointer.has_value() || !previousPointer.has_value()) {
+ continue;
+ }
+ const PointerCoords& pointerCoords =
+ *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex));
+ if (equalXY(pointerCoords, lastRealPointer->coords)) {
+ LOG_IF(INFO, debugResampling())
+ << "Pointer ID: " << motionEvent.getPointerId(pointerIndex)
+ << " did not move. Overwriting its coordinates from " << pointerCoords << " to "
+ << previousPointer->coords;
+ setMotionEventPointerCoords(motionEvent, sampleIndex, pointerIndex,
+ previousPointer->coords);
+ }
}
- mPreviousDeviceId = motionEvent.getDeviceId();
+}
+void LegacyResampler::overwriteOldPointers(MotionEvent& motionEvent, size_t sampleIndex) const {
+ if (!mPreviousPrediction.has_value()) {
+ LOG_IF(INFO, debugResampling()) << "Old sample not overwritten. Not enough data.";
+ return;
+ }
+ if (nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)} <
+ mPreviousPrediction->eventTime) {
+ LOG_IF(INFO, debugResampling())
+ << "Motion event sample older than predicted sample. Overwriting event time from "
+ << std::setprecision(3)
+ << std::chrono::duration<double,
+ std::milli>{nanoseconds{motionEvent.getHistoricalEventTime(
+ sampleIndex)}}
+ .count()
+ << "ms to "
+ << std::chrono::duration<double, std::milli>{mPreviousPrediction->eventTime}.count()
+ << "ms";
+ for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
+ ++pointerIndex) {
+ const std::optional<Pointer> previousPointer = mPreviousPrediction->pointerMap.find(
+ PointerMap::PointerId{motionEvent.getPointerId(pointerIndex)});
+ // This could happen because resampler only receives ACTION_MOVE events.
+ if (!previousPointer.has_value()) {
+ continue;
+ }
+ setMotionEventPointerCoords(motionEvent, sampleIndex, pointerIndex,
+ previousPointer->coords);
+ }
+ }
+}
+
+void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& motionEvent,
+ const InputMessage* futureSample) {
const nanoseconds resampleTime = frameTime - RESAMPLE_LATENCY;
+ if (resampleTime.count() == motionEvent.getEventTime()) {
+ LOG_IF(INFO, debugResampling()) << "Not resampled. Resample time equals motion event time.";
+ return;
+ }
+
updateLatestSamples(motionEvent);
const std::optional<Sample> sample = (futureSample != nullptr)
@@ -261,6 +376,47 @@ void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& mo
: (attemptExtrapolation(resampleTime));
if (sample.has_value()) {
addSampleToMotionEvent(*sample, motionEvent);
+ if (mPreviousPrediction.has_value()) {
+ overwriteMotionEventSamples(motionEvent);
+ }
+ // mPreviousPrediction is only updated whenever extrapolation occurs because extrapolation
+ // is about predicting upcoming scenarios.
+ if (futureSample == nullptr) {
+ mPreviousPrediction = sample;
+ }
+ }
+ LOG_IF(FATAL, mLatestSamples.empty()) << "mLatestSamples must contain at least one sample.";
+ mLastRealSample = *(mLatestSamples.end() - 1);
+}
+
+// --- FilteredLegacyResampler ---
+
+FilteredLegacyResampler::FilteredLegacyResampler(float minCutoffFreq, float beta)
+ : mResampler{}, mMinCutoffFreq{minCutoffFreq}, mBeta{beta} {}
+
+void FilteredLegacyResampler::resampleMotionEvent(std::chrono::nanoseconds requestedFrameTime,
+ MotionEvent& motionEvent,
+ const InputMessage* futureSample) {
+ mResampler.resampleMotionEvent(requestedFrameTime, motionEvent, futureSample);
+ const size_t numSamples = motionEvent.getHistorySize() + 1;
+ for (size_t sampleIndex = 0; sampleIndex < numSamples; ++sampleIndex) {
+ for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
+ ++pointerIndex) {
+ const int32_t pointerId = motionEvent.getPointerProperties(pointerIndex)->id;
+ const nanoseconds eventTime =
+ nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)};
+ // Refer to the static function `setMotionEventPointerCoords` for a justification of
+ // casting away const.
+ PointerCoords& pointerCoords = const_cast<PointerCoords&>(
+ *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, sampleIndex)));
+ const auto& [iter, _] = mFilteredPointers.try_emplace(pointerId, mMinCutoffFreq, mBeta);
+ iter->second.filter(eventTime, pointerCoords);
+ }
}
}
+
+std::chrono::nanoseconds FilteredLegacyResampler::getResampleLatency() const {
+ return mResampler.getResampleLatency();
+}
+
} // namespace android
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index e23fc94c5e..4b87dab01b 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -43,24 +43,46 @@ interface IInputConstants
const int INVALID_INPUT_DEVICE_ID = -2;
/**
- * The input event was injected from accessibility. Used in policyFlags for input event
- * injection.
+ * The input event was injected from some AccessibilityService, which may be either an
+ * Accessibility Tool OR a service using that API for purposes other than assisting users with
+ * disabilities. Used in policyFlags for input event injection.
*/
const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000;
/**
+ * The key event triggered a key gesture. Used in policy flag to notify that a key gesture was
+ * triggered using the event.
+ */
+ const int POLICY_FLAG_KEY_GESTURE_TRIGGERED = 0x40000;
+
+ /**
+ * The input event was injected from an AccessibilityService with the
+ * AccessibilityServiceInfo#isAccessibilityTool property set to true. These services (known as
+ * "Accessibility Tools") are used to assist users with disabilities, so events from these
+ * services should be able to reach all Views including Views which set
+ * View#isAccessibilityDataSensitive to true. Used in policyFlags for input event injection.
+ */
+ const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL = 0x80000;
+
+ /**
* Common input event flag used for both motion and key events for a gesture or pointer being
* canceled.
*/
const int INPUT_EVENT_FLAG_CANCELED = 0x20;
/**
- * Common input event flag used for both motion and key events, indicating that the event
- * was generated or modified by accessibility service.
+ * Input event flag used for both motion and key events.
+ * See POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY for more information.
*/
const int INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800;
/**
+ * Input event flag used for motion events.
+ * See POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL for more information.
+ */
+ const int INPUT_EVENT_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL = 0x1000;
+
+ /**
* Common input event flag used for both motion and key events, indicating that the system has
* detected this event may be inconsistent with the current event sequence or gesture, such as
* when a pointer move event is sent but the pointer is not down.
@@ -70,6 +92,9 @@ interface IInputConstants
/* The default pointer acceleration value. */
const int DEFAULT_POINTER_ACCELERATION = 3;
+ /* The default mouse wheel acceleration value. */
+ const int DEFAULT_MOUSE_WHEEL_ACCELERATION = 4;
+
/**
* Use the default Velocity Tracker Strategy. Different axes may use different default
* strategies.
diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl
index da62e03821..e5f7b56561 100644
--- a/libs/input/android/os/InputConfig.aidl
+++ b/libs/input/android/os/InputConfig.aidl
@@ -57,16 +57,9 @@ enum InputConfig {
NOT_TOUCHABLE = 1 << 3,
/**
- * Indicates that this window will not accept a touch event that is split between
- * more than one window. When set:
- * - If this window receives a DOWN event with the first pointer, all successive
- * pointers that go down, regardless of their location on the screen, will be
- * directed to this window;
- * - If the DOWN event lands outside the touchable bounds of this window, no
- * successive pointers that go down, regardless of their location on the screen,
- * will be directed to this window.
- */
- PREVENT_SPLITTING = 1 << 4,
+ * This flag is now deprecated and should not be used.
+ */
+ DEPRECATED_PREVENT_SPLITTING = 1 << 4,
/**
* Indicates that this window shows the wallpaper behind it, so all touch events
diff --git a/libs/input/android/os/MotionEventFlag.aidl b/libs/input/android/os/MotionEventFlag.aidl
index 2093b0636a..6c9ccfbd04 100644
--- a/libs/input/android/os/MotionEventFlag.aidl
+++ b/libs/input/android/os/MotionEventFlag.aidl
@@ -118,13 +118,24 @@ enum MotionEventFlag {
PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = 0x100,
/**
- * The input event was generated or modified by accessibility service.
- * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
- * set of flags, including in input/Input.h and in android/input.h.
+ * The input event was injected from some AccessibilityService, which may be either an
+ * Accessibility Tool OR a service using that API for purposes other than assisting users with
+ * disabilities. Shared by both KeyEvent and MotionEvent flags, so this value should not
+ * overlap with either set of flags, including in input/Input.h and in android/input.h.
*/
IS_ACCESSIBILITY_EVENT = IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
/**
+ * The input event was injected from an AccessibilityService with the
+ * AccessibilityServiceInfo#isAccessibilityTool property set to true. These services (known as
+ * "Accessibility Tools") are used to assist users with disabilities, so events from these
+ * services should be able to reach all Views including Views which set
+ * View#isAccessibilityDataSensitive to true. Only used by MotionEvent flags.
+ */
+ INJECTED_FROM_ACCESSIBILITY_TOOL =
+ IInputConstants.INPUT_EVENT_FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL,
+
+ /**
* Private flag that indicates when the system has detected that this motion event
* may be inconsistent with respect to the sequence of previously delivered motion events,
* such as when a pointer move event is sent but the pointer is not down.
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 60fb00e128..9d387fe1ec 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -16,6 +16,16 @@ flag {
}
flag {
+ name: "enable_button_state_verification"
+ namespace: "input"
+ description: "Set to true to enable crashing whenever bad inbound events are going into InputDispatcher"
+ bug: "392870542"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "remove_input_channel_from_windowstate"
namespace: "input"
description: "Do not store a copy of input channel inside WindowState."
@@ -37,9 +47,9 @@ flag {
}
flag {
- name: "split_all_touches"
+ name: "deprecate_split_touch_apis"
namespace: "input"
- description: "Set FLAG_SPLIT_TOUCHES to true for all windows, regardless of what they specify. This is essentially deprecating this flag by forcefully enabling the split functionality"
+ description: "Deprecate all public APIs related to split touch because now all windows behave as if split touch is permanently enabled and there's no way for a window to disable split touch."
bug: "239934827"
}
@@ -51,13 +61,6 @@ flag {
}
flag {
- name: "report_palms_to_gestures_library"
- namespace: "input"
- description: "Report touches marked as palm by firmware to gestures library"
- bug: "302505955"
-}
-
-flag {
name: "enable_touchpad_typing_palm_rejection"
namespace: "input"
description: "Enabling additional touchpad palm rejection will disable the tap to click while the user is typing on a physical keyboard"
@@ -79,13 +82,6 @@ flag {
}
flag {
- name: "enable_input_filter_rust_impl"
- namespace: "input"
- description: "Enable input filter rust implementation"
- bug: "294546335"
-}
-
-flag {
name: "override_key_behavior_permission_apis"
is_exported: true
namespace: "input"
@@ -94,13 +90,6 @@ flag {
}
flag {
- name: "enable_new_mouse_pointer_ballistics"
- namespace: "input"
- description: "Change the acceleration curves for mouse pointer movements to match the touchpad ones"
- bug: "315313622"
-}
-
-flag {
name: "rate_limit_user_activity_poke_in_dispatcher"
namespace: "input"
description: "Move user-activity poke rate-limiting from PowerManagerService to InputDispatcher."
@@ -116,13 +105,6 @@ flag {
}
flag {
- name: "enable_touchpad_fling_stop"
- namespace: "input"
- description: "Enable fling scrolling to be stopped by putting a finger on the touchpad again"
- bug: "281106755"
-}
-
-flag {
name: "enable_prediction_pruning_via_jerk_thresholding"
namespace: "input"
description: "Enable prediction pruning based on jerk thresholds."
@@ -202,8 +184,86 @@ flag {
}
flag {
+ name: "disable_touch_input_mapper_pointer_usage"
+ namespace: "input"
+ description: "Disable the PointerUsage concept in TouchInputMapper since the old touchpad stack is no longer used."
+ bug: "281840344"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "keyboard_repeat_keys"
namespace: "input"
description: "Allow user to enable key repeats or configure timeout before key repeat and key repeat delay rates."
bug: "336585002"
}
+
+flag {
+ name: "rotary_input_telemetry"
+ namespace: "wear_frameworks"
+ description: "Enable telemetry for rotary input"
+ bug: "370353565"
+}
+
+flag {
+ name: "set_input_device_kernel_wake"
+ namespace: "input"
+ description: "Set input device's power/wakeup sysfs node"
+ bug: "372812925"
+}
+
+flag {
+ name: "enable_alphabetic_keyboard_wake"
+ namespace: "input"
+ description: "Enable wake from alphabetic keyboards."
+ bug: "352856881"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "connected_displays_cursor"
+ namespace: "lse_desktop_experience"
+ description: "Allow cursor to transition across multiple connected displays"
+ bug: "362719483"
+}
+
+flag {
+ name: "connected_displays_associated_display_cursor_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Apply some rules to define associated display cursor behavior in connected displays"
+ bug: "396568321"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "use_cloned_screen_coordinates_as_raw"
+ namespace: "input"
+ description: "Use the cloned window's layer stack (screen) space as the raw coordinate space for input going to clones"
+ bug: "377846505"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "deprecate_uiautomation_input_injection"
+ namespace: "input"
+ description: "Deprecate UiAutomation#injectInputEvent and UiAutomation#injectInputEventToInputFilter test/hidden APIs"
+ bug: "277261245"
+}
+
+flag {
+ name: "prevent_merging_input_pointer_devices"
+ namespace: "desktop_input"
+ description: "Prevent merging input sub-devices that provide pointer input streams"
+ bug: "389689566"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/libs/input/rust/Android.bp b/libs/input/rust/Android.bp
index 63853f77fa..fae9074c4d 100644
--- a/libs/input/rust/Android.bp
+++ b/libs/input/rust/Android.bp
@@ -18,12 +18,12 @@ rust_defaults {
srcs: ["lib.rs"],
host_supported: true,
rustlibs: [
+ "inputconstants-rust",
"libbitflags",
"libcxx",
"libinput_bindgen",
- "liblogger",
"liblog_rust",
- "inputconstants-rust",
+ "liblogger",
"libserde",
"libserde_json",
],
diff --git a/libs/input/rust/data_store.rs b/libs/input/rust/data_store.rs
index 6bdcefda36..beb6e23478 100644
--- a/libs/input/rust/data_store.rs
+++ b/libs/input/rust/data_store.rs
@@ -17,7 +17,7 @@
//! Contains the DataStore, used to store input related data in a persistent way.
use crate::input::KeyboardType;
-use log::{debug, error};
+use log::{debug, error, info};
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::{Read, Write};
@@ -157,7 +157,7 @@ impl FileReaderWriter for DefaultFileReaderWriter {
let path = Path::new(&self.filepath);
let mut fs_string = String::new();
match File::open(path) {
- Err(e) => error!("couldn't open {:?}: {}", path, e),
+ Err(e) => info!("couldn't open {:?}: {}", path, e),
Ok(mut file) => match file.read_to_string(&mut fs_string) {
Err(e) => error!("Couldn't read from {:?}: {}", path, e),
Ok(_) => debug!("Successfully read from file {:?}", path),
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 90f509d97f..35ba04f5b6 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -50,7 +50,7 @@ pub enum SourceClass {
bitflags! {
/// Source of the input device or input events.
- #[derive(Debug, PartialEq)]
+ #[derive(Clone, Copy, Debug, PartialEq)]
pub struct Source: u32 {
// Constants from SourceClass, added here for compatibility reasons
/// SourceClass::Button
@@ -101,6 +101,7 @@ bitflags! {
/// A rust enum representation of a MotionEvent action.
#[repr(u32)]
+#[derive(Clone, Copy, Eq, PartialEq)]
pub enum MotionAction {
/// ACTION_DOWN
Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
@@ -131,9 +132,15 @@ pub enum MotionAction {
/// ACTION_SCROLL
Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
/// ACTION_BUTTON_PRESS
- ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+ ButtonPress {
+ /// The button being pressed.
+ action_button: MotionButton,
+ } = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
/// ACTION_BUTTON_RELEASE
- ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+ ButtonRelease {
+ /// The button being released.
+ action_button: MotionButton,
+ } = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
}
impl fmt::Display for MotionAction {
@@ -152,14 +159,20 @@ impl fmt::Display for MotionAction {
MotionAction::Scroll => write!(f, "SCROLL"),
MotionAction::HoverEnter => write!(f, "HOVER_ENTER"),
MotionAction::HoverExit => write!(f, "HOVER_EXIT"),
- MotionAction::ButtonPress => write!(f, "BUTTON_PRESS"),
- MotionAction::ButtonRelease => write!(f, "BUTTON_RELEASE"),
+ MotionAction::ButtonPress { action_button } => {
+ write!(f, "BUTTON_PRESS({action_button:?})")
+ }
+ MotionAction::ButtonRelease { action_button } => {
+ write!(f, "BUTTON_RELEASE({action_button:?})")
+ }
}
}
}
-impl From<u32> for MotionAction {
- fn from(action: u32) -> Self {
+impl MotionAction {
+ /// Creates a [`MotionAction`] from an `AMOTION_EVENT_ACTION_…` constant and an action button
+ /// (which should be empty for all actions except `BUTTON_PRESS` and `…_RELEASE`).
+ pub fn from_code(action: u32, action_button: MotionButton) -> Self {
let (action_masked, action_index) = MotionAction::breakdown_action(action);
match action_masked {
input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
@@ -177,14 +190,16 @@ impl From<u32> for MotionAction {
input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
- input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
- input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
+ input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => {
+ MotionAction::ButtonPress { action_button }
+ }
+ input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => {
+ MotionAction::ButtonRelease { action_button }
+ }
_ => panic!("Unknown action: {}", action),
}
}
-}
-impl MotionAction {
fn breakdown_action(action: u32) -> (u32, usize) {
let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
@@ -194,10 +209,31 @@ impl MotionAction {
}
bitflags! {
+ /// MotionEvent buttons.
+ #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
+ pub struct MotionButton: u32 {
+ /// Primary button (e.g. the left mouse button)
+ const Primary = input_bindgen::AMOTION_EVENT_BUTTON_PRIMARY;
+ /// Secondary button (e.g. the right mouse button)
+ const Secondary = input_bindgen::AMOTION_EVENT_BUTTON_SECONDARY;
+ /// Tertiary button (e.g. the middle mouse button)
+ const Tertiary = input_bindgen::AMOTION_EVENT_BUTTON_TERTIARY;
+ /// Back button
+ const Back = input_bindgen::AMOTION_EVENT_BUTTON_BACK;
+ /// Forward button
+ const Forward = input_bindgen::AMOTION_EVENT_BUTTON_FORWARD;
+ /// Primary stylus button
+ const StylusPrimary = input_bindgen::AMOTION_EVENT_BUTTON_STYLUS_PRIMARY;
+ /// Secondary stylus button
+ const StylusSecondary = input_bindgen::AMOTION_EVENT_BUTTON_STYLUS_SECONDARY;
+ }
+}
+
+bitflags! {
/// MotionEvent flags.
/// The source of truth for the flag definitions are the MotionEventFlag AIDL enum.
/// The flag values are redefined here as a bitflags API.
- #[derive(Debug)]
+ #[derive(Clone, Copy, Debug)]
pub struct MotionFlags: u32 {
/// FLAG_WINDOW_IS_OBSCURED
const WINDOW_IS_OBSCURED = MotionEventFlag::WINDOW_IS_OBSCURED.0 as u32;
@@ -219,6 +255,9 @@ bitflags! {
MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION.0 as u32;
/// FLAG_IS_ACCESSIBILITY_EVENT
const IS_ACCESSIBILITY_EVENT = MotionEventFlag::IS_ACCESSIBILITY_EVENT.0 as u32;
+ /// FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL
+ const INJECTED_FROM_ACCESSIBILITY_TOOL =
+ MotionEventFlag::INJECTED_FROM_ACCESSIBILITY_TOOL.0 as u32;
/// FLAG_TAINTED
const TAINTED = MotionEventFlag::TAINTED.0 as u32;
/// FLAG_TARGET_ACCESSIBILITY_FOCUS
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
index b1d7760682..f87dd413c6 100644
--- a/libs/input/rust/input_verifier.rs
+++ b/libs/input/rust/input_verifier.rs
@@ -17,21 +17,55 @@
//! Contains the InputVerifier, used to validate a stream of input events.
use crate::ffi::RustPointerProperties;
-use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass};
+use crate::input::{DeviceId, MotionAction, MotionButton, MotionFlags, Source, SourceClass};
use log::info;
use std::collections::HashMap;
use std::collections::HashSet;
-fn verify_event(
- action: MotionAction,
- pointer_properties: &[RustPointerProperties],
- flags: &MotionFlags,
-) -> Result<(), String> {
- let pointer_count = pointer_properties.len();
+/// Represents a movement or state change event from a pointer device. The Rust equivalent of the
+/// C++ NotifyMotionArgs struct.
+#[derive(Clone, Copy)]
+pub struct NotifyMotionArgs<'a> {
+ /// The ID of the device that emitted the event.
+ pub device_id: DeviceId,
+
+ /// The type of device that emitted the event.
+ pub source: Source,
+
+ /// The type of event that took place.
+ pub action: MotionAction,
+
+ /// The properties of each of the pointers involved in the event.
+ pub pointer_properties: &'a [RustPointerProperties],
+
+ /// Flags applied to the event.
+ pub flags: MotionFlags,
+
+ /// The set of buttons that were pressed at the time of the event.
+ ///
+ /// We allow DOWN events to include buttons in their state for which BUTTON_PRESS events haven't
+ /// been sent yet. In that case, the DOWN should be immediately followed by BUTTON_PRESS events
+ /// for those buttons, building up to a button state matching that of the DOWN. For example, if
+ /// the user presses the primary and secondary buttons at exactly the same time, we'd expect
+ /// this sequence:
+ ///
+ /// | Action | Action button | Button state |
+ /// |----------------|---------------|------------------------|
+ /// | `HOVER_EXIT` | - | - |
+ /// | `DOWN` | - | `PRIMARY`, `SECONDARY` |
+ /// | `BUTTON_PRESS` | `PRIMARY` | `PRIMARY` |
+ /// | `BUTTON_PRESS` | `SECONDARY` | `PRIMARY`, `SECONDARY` |
+ /// | `MOVE` | - | `PRIMARY`, `SECONDARY` |
+ pub button_state: MotionButton,
+}
+
+/// Verifies the properties of an event that should always be true, regardless of the current state.
+fn verify_event(event: NotifyMotionArgs<'_>, verify_buttons: bool) -> Result<(), String> {
+ let pointer_count = event.pointer_properties.len();
if pointer_count < 1 {
- return Err(format!("Invalid {} event: no pointers", action));
+ return Err(format!("Invalid {} event: no pointers", event.action));
}
- match action {
+ match event.action {
MotionAction::Down
| MotionAction::HoverEnter
| MotionAction::HoverExit
@@ -40,23 +74,40 @@ fn verify_event(
if pointer_count != 1 {
return Err(format!(
"Invalid {} event: there are {} pointers in the event",
- action, pointer_count
+ event.action, pointer_count
));
}
}
MotionAction::Cancel => {
- if !flags.contains(MotionFlags::CANCELED) {
+ if !event.flags.contains(MotionFlags::CANCELED) {
return Err(format!(
"For ACTION_CANCEL, must set FLAG_CANCELED. Received flags: {:#?}",
- flags
+ event.flags
));
}
}
MotionAction::PointerDown { action_index } | MotionAction::PointerUp { action_index } => {
if action_index >= pointer_count {
- return Err(format!("Got {}, but event has {} pointer(s)", action, pointer_count));
+ return Err(format!(
+ "Got {}, but event has {} pointer(s)",
+ event.action, pointer_count
+ ));
+ }
+ }
+
+ MotionAction::ButtonPress { action_button }
+ | MotionAction::ButtonRelease { action_button } => {
+ if verify_buttons {
+ let button_count = action_button.iter().count();
+ if button_count != 1 {
+ return Err(format!(
+ "Invalid {} event: must specify a single action button, not {} action \
+ buttons",
+ event.action, button_count
+ ));
+ }
}
}
@@ -65,17 +116,94 @@ fn verify_event(
Ok(())
}
+/// Keeps track of the button state for a single device and verifies events against it.
+#[derive(Default)]
+struct ButtonVerifier {
+ /// The current button state of the device.
+ button_state: MotionButton,
+
+ /// The set of "pending buttons", which were seen in the last DOWN event's button state but
+ /// for which we haven't seen BUTTON_PRESS events yet (see [`NotifyMotionArgs::button_state`]).
+ pending_buttons: MotionButton,
+}
+
+impl ButtonVerifier {
+ pub fn process_event(&mut self, event: NotifyMotionArgs<'_>) -> Result<(), String> {
+ if !self.pending_buttons.is_empty() {
+ // We just saw a DOWN with some additional buttons in its state, so it should be
+ // immediately followed by ButtonPress events for those buttons.
+ match event.action {
+ MotionAction::ButtonPress { action_button }
+ if self.pending_buttons.contains(action_button) =>
+ {
+ self.pending_buttons -= action_button;
+ }
+ _ => {
+ return Err(format!(
+ "After DOWN event, expected BUTTON_PRESS event(s) for {:?}, but got {}",
+ self.pending_buttons, event.action
+ ));
+ }
+ }
+ }
+ let expected_state = match event.action {
+ MotionAction::Down => {
+ if self.button_state - event.button_state != MotionButton::empty() {
+ return Err(format!(
+ "DOWN event button state is missing {:?}",
+ self.button_state - event.button_state
+ ));
+ }
+ self.pending_buttons = event.button_state - self.button_state;
+ // We've already checked that the state isn't missing any already-down buttons, and
+ // extra buttons are valid on DOWN actions, so bypass the expected state check.
+ event.button_state
+ }
+ MotionAction::ButtonPress { action_button } => {
+ if self.button_state.contains(action_button) {
+ return Err(format!(
+ "Duplicate BUTTON_PRESS; button state already contains {action_button:?}"
+ ));
+ }
+ self.button_state | action_button
+ }
+ MotionAction::ButtonRelease { action_button } => {
+ if !self.button_state.contains(action_button) {
+ return Err(format!(
+ "Invalid BUTTON_RELEASE; button state doesn't contain {action_button:?}",
+ ));
+ }
+ self.button_state - action_button
+ }
+ _ => self.button_state,
+ };
+ if event.button_state != expected_state {
+ return Err(format!(
+ "Expected {} button state to be {:?}, but was {:?}",
+ event.action, expected_state, event.button_state
+ ));
+ }
+ // DOWN events can have pending buttons, so don't update the state for them.
+ if event.action != MotionAction::Down {
+ self.button_state = event.button_state;
+ }
+ Ok(())
+ }
+}
+
/// The InputVerifier is used to validate a stream of input events.
pub struct InputVerifier {
name: String,
should_log: bool,
+ verify_buttons: bool,
touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
hovering_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+ button_verifier_by_device: HashMap<DeviceId, ButtonVerifier>,
}
impl InputVerifier {
/// Create a new InputVerifier.
- pub fn new(name: &str, should_log: bool) -> Self {
+ pub fn new(name: &str, should_log: bool, verify_buttons: bool) -> Self {
logger::init(
logger::Config::default()
.with_tag_on_device("InputVerifier")
@@ -84,68 +212,70 @@ impl InputVerifier {
Self {
name: name.to_owned(),
should_log,
+ verify_buttons,
touching_pointer_ids_by_device: HashMap::new(),
hovering_pointer_ids_by_device: HashMap::new(),
+ button_verifier_by_device: HashMap::new(),
}
}
/// Process a pointer movement event from an InputDevice.
/// If the event is not valid, we return an error string that describes the issue.
- pub fn process_movement(
- &mut self,
- device_id: DeviceId,
- source: Source,
- action: u32,
- pointer_properties: &[RustPointerProperties],
- flags: MotionFlags,
- ) -> Result<(), String> {
- if !source.is_from_class(SourceClass::Pointer) {
+ pub fn process_movement(&mut self, event: NotifyMotionArgs<'_>) -> Result<(), String> {
+ if !event.source.is_from_class(SourceClass::Pointer) {
// Skip non-pointer sources like MOUSE_RELATIVE for now
return Ok(());
}
if self.should_log {
info!(
"Processing {} for device {:?} ({} pointer{}) on {}",
- MotionAction::from(action).to_string(),
- device_id,
- pointer_properties.len(),
- if pointer_properties.len() == 1 { "" } else { "s" },
+ event.action,
+ event.device_id,
+ event.pointer_properties.len(),
+ if event.pointer_properties.len() == 1 { "" } else { "s" },
self.name
);
}
- verify_event(action.into(), pointer_properties, &flags)?;
+ verify_event(event, self.verify_buttons)?;
+
+ if self.verify_buttons {
+ self.button_verifier_by_device
+ .entry(event.device_id)
+ .or_default()
+ .process_event(event)?;
+ }
- match action.into() {
+ match event.action {
MotionAction::Down => {
- if self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ if self.touching_pointer_ids_by_device.contains_key(&event.device_id) {
return Err(format!(
"{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
- self.name, device_id, self.touching_pointer_ids_by_device
+ self.name, event.device_id, self.touching_pointer_ids_by_device
));
}
- let it = self.touching_pointer_ids_by_device.entry(device_id).or_default();
- it.insert(pointer_properties[0].id);
+ let it = self.touching_pointer_ids_by_device.entry(event.device_id).or_default();
+ it.insert(event.pointer_properties[0].id);
}
MotionAction::PointerDown { action_index } => {
- if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ if !self.touching_pointer_ids_by_device.contains_key(&event.device_id) {
return Err(format!(
"{}: Received POINTER_DOWN but no pointers are currently down \
for device {:?}",
- self.name, device_id
+ self.name, event.device_id
));
}
- let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
- if it.len() != pointer_properties.len() - 1 {
+ let it = self.touching_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
+ if it.len() != event.pointer_properties.len() - 1 {
return Err(format!(
"{}: There are currently {} touching pointers, but the incoming \
POINTER_DOWN event has {}",
self.name,
it.len(),
- pointer_properties.len()
+ event.pointer_properties.len()
));
}
- let pointer_id = pointer_properties[action_index].id;
+ let pointer_id = event.pointer_properties[action_index].id;
if it.contains(&pointer_id) {
return Err(format!(
"{}: Pointer with id={} already present found in the properties",
@@ -155,7 +285,7 @@ impl InputVerifier {
it.insert(pointer_id);
}
MotionAction::Move => {
- if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ if !self.ensure_touching_pointers_match(event.device_id, event.pointer_properties) {
return Err(format!(
"{}: ACTION_MOVE touching pointers don't match",
self.name
@@ -163,49 +293,49 @@ impl InputVerifier {
}
}
MotionAction::PointerUp { action_index } => {
- if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ if !self.ensure_touching_pointers_match(event.device_id, event.pointer_properties) {
return Err(format!(
"{}: ACTION_POINTER_UP touching pointers don't match",
self.name
));
}
- let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
- let pointer_id = pointer_properties[action_index].id;
+ let it = self.touching_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
+ let pointer_id = event.pointer_properties[action_index].id;
it.remove(&pointer_id);
}
MotionAction::Up => {
- if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ if !self.touching_pointer_ids_by_device.contains_key(&event.device_id) {
return Err(format!(
"{} Received ACTION_UP but no pointers are currently down for device {:?}",
- self.name, device_id
+ self.name, event.device_id
));
}
- let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ let it = self.touching_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
if it.len() != 1 {
return Err(format!(
"{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
- self.name, it, device_id
+ self.name, it, event.device_id
));
}
- let pointer_id = pointer_properties[0].id;
+ let pointer_id = event.pointer_properties[0].id;
if !it.contains(&pointer_id) {
return Err(format!(
"{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
{:?} for device {:?}",
- self.name, pointer_id, it, device_id
+ self.name, pointer_id, it, event.device_id
));
}
- self.touching_pointer_ids_by_device.remove(&device_id);
+ self.touching_pointer_ids_by_device.remove(&event.device_id);
}
MotionAction::Cancel => {
- if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ if !self.ensure_touching_pointers_match(event.device_id, event.pointer_properties) {
return Err(format!(
"{}: Got ACTION_CANCEL, but the pointers don't match. \
Existing pointers: {:?}",
self.name, self.touching_pointer_ids_by_device
));
}
- self.touching_pointer_ids_by_device.remove(&device_id);
+ self.touching_pointer_ids_by_device.remove(&event.device_id);
}
/*
* The hovering protocol currently supports a single pointer only, because we do not
@@ -214,41 +344,41 @@ impl InputVerifier {
* eventually supported.
*/
MotionAction::HoverEnter => {
- if self.hovering_pointer_ids_by_device.contains_key(&device_id) {
+ if self.hovering_pointer_ids_by_device.contains_key(&event.device_id) {
return Err(format!(
"{}: Invalid HOVER_ENTER event - pointers already hovering for device {:?}:\
{:?}",
- self.name, device_id, self.hovering_pointer_ids_by_device
+ self.name, event.device_id, self.hovering_pointer_ids_by_device
));
}
- let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
- it.insert(pointer_properties[0].id);
+ let it = self.hovering_pointer_ids_by_device.entry(event.device_id).or_default();
+ it.insert(event.pointer_properties[0].id);
}
MotionAction::HoverMove => {
// For compatibility reasons, we allow HOVER_MOVE without a prior HOVER_ENTER.
// If there was no prior HOVER_ENTER, just start a new hovering pointer.
- let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
- it.insert(pointer_properties[0].id);
+ let it = self.hovering_pointer_ids_by_device.entry(event.device_id).or_default();
+ it.insert(event.pointer_properties[0].id);
}
MotionAction::HoverExit => {
- if !self.hovering_pointer_ids_by_device.contains_key(&device_id) {
+ if !self.hovering_pointer_ids_by_device.contains_key(&event.device_id) {
return Err(format!(
"{}: Invalid HOVER_EXIT event - no pointers are hovering for device {:?}",
- self.name, device_id
+ self.name, event.device_id
));
}
- let pointer_id = pointer_properties[0].id;
- let it = self.hovering_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ let pointer_id = event.pointer_properties[0].id;
+ let it = self.hovering_pointer_ids_by_device.get_mut(&event.device_id).unwrap();
it.remove(&pointer_id);
if !it.is_empty() {
return Err(format!(
"{}: Removed hovering pointer {}, but pointers are still\
hovering for device {:?}: {:?}",
- self.name, pointer_id, device_id, it
+ self.name, pointer_id, event.device_id, it
));
}
- self.hovering_pointer_ids_by_device.remove(&device_id);
+ self.hovering_pointer_ids_by_device.remove(&event.device_id);
}
_ => return Ok(()),
}
@@ -288,295 +418,227 @@ impl InputVerifier {
#[cfg(test)]
mod tests {
+ use crate::input::MotionButton;
use crate::input_verifier::InputVerifier;
+ use crate::input_verifier::NotifyMotionArgs;
use crate::DeviceId;
+ use crate::MotionAction;
use crate::MotionFlags;
use crate::RustPointerProperties;
use crate::Source;
+ const BASE_POINTER_PROPERTIES: [RustPointerProperties; 1] = [RustPointerProperties { id: 0 }];
+ const BASE_EVENT: NotifyMotionArgs = NotifyMotionArgs {
+ device_id: DeviceId(1),
+ source: Source::Touchscreen,
+ action: MotionAction::Down,
+ pointer_properties: &BASE_POINTER_PROPERTIES,
+ flags: MotionFlags::empty(),
+ button_state: MotionButton::empty(),
+ };
+ const BASE_MOUSE_EVENT: NotifyMotionArgs =
+ NotifyMotionArgs { source: Source::Mouse, ..BASE_EVENT };
+
#[test]
/**
* Send a DOWN event with 2 pointers and ensure that it's marked as invalid.
*/
fn bad_down_event() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ true);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ true, /*verify_buttons*/ true);
let pointer_properties =
Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Down,
+ pointer_properties: &pointer_properties,
+ ..BASE_EVENT
+ })
.is_err());
}
#[test]
fn single_pointer_stream() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Down,
+ pointer_properties: &pointer_properties,
+ ..BASE_EVENT
+ })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Move,
+ pointer_properties: &pointer_properties,
+ ..BASE_EVENT
+ })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_UP,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Up,
+ pointer_properties: &pointer_properties,
+ ..BASE_EVENT
+ })
.is_ok());
}
#[test]
fn two_pointer_stream() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Down,
+ pointer_properties: &pointer_properties,
+ ..BASE_EVENT
+ })
.is_ok());
// POINTER 1 DOWN
let two_pointer_properties =
Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
- | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- &two_pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::PointerDown { action_index: 1 },
+ pointer_properties: &two_pointer_properties,
+ ..BASE_EVENT
+ })
.is_ok());
// POINTER 0 UP
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP
- | (0 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- &two_pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::PointerUp { action_index: 0 },
+ pointer_properties: &two_pointer_properties,
+ ..BASE_EVENT
+ })
.is_ok());
// ACTION_UP for pointer id=1
let pointer_1_properties = Vec::from([RustPointerProperties { id: 1 }]);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_UP,
- &pointer_1_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Up,
+ pointer_properties: &pointer_1_properties,
+ ..BASE_EVENT
+ })
.is_ok());
}
#[test]
fn multi_device_stream() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ device_id: DeviceId(1),
+ action: MotionAction::Down,
+ ..BASE_EVENT
+ })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ device_id: DeviceId(1),
+ action: MotionAction::Move,
+ ..BASE_EVENT
+ })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(2),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ device_id: DeviceId(2),
+ action: MotionAction::Down,
+ ..BASE_EVENT
+ })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(2),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ device_id: DeviceId(2),
+ action: MotionAction::Move,
+ ..BASE_EVENT
+ })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_UP,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ device_id: DeviceId(1),
+ action: MotionAction::Up,
+ ..BASE_EVENT
+ })
.is_ok());
}
#[test]
fn action_cancel() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Down,
+ flags: MotionFlags::empty(),
+ ..BASE_EVENT
+ })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
- &pointer_properties,
- MotionFlags::CANCELED,
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Cancel,
+ flags: MotionFlags::CANCELED,
+ ..BASE_EVENT
+ })
.is_ok());
}
#[test]
fn invalid_action_cancel() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- MotionFlags::empty(),
- )
- .is_ok());
- assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
- &pointer_properties,
- MotionFlags::empty(), // forgot to set FLAG_CANCELED
- )
+ .process_movement(NotifyMotionArgs { action: MotionAction::Down, ..BASE_EVENT })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs { action: MotionAction::Cancel, ..BASE_EVENT })
.is_err());
}
#[test]
fn invalid_up() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_UP,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs { action: MotionAction::Up, ..BASE_EVENT })
.is_err());
}
#[test]
fn correct_hover_sequence() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs { action: MotionAction::HoverMove, ..BASE_EVENT })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs { action: MotionAction::HoverExit, ..BASE_EVENT })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
.is_ok());
}
#[test]
fn double_hover_enter() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
.is_ok());
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs { action: MotionAction::HoverEnter, ..BASE_EVENT })
.is_err());
}
@@ -584,55 +646,356 @@ mod tests {
// MOUSE_RELATIVE, which is used during pointer capture. The verifier should allow such event.
#[test]
fn relative_mouse_move() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
- let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
assert!(verifier
- .process_movement(
- DeviceId(2),
- Source::MouseRelative,
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ device_id: DeviceId(2),
+ source: Source::MouseRelative,
+ action: MotionAction::Move,
+ ..BASE_EVENT
+ })
.is_ok());
}
// Send a MOVE event with incorrect number of pointers (one of the pointers is missing).
#[test]
fn move_with_wrong_number_of_pointers() {
- let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_DOWN,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Down,
+ pointer_properties: &pointer_properties,
+ ..BASE_EVENT
+ })
.is_ok());
// POINTER 1 DOWN
let two_pointer_properties =
Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
- | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
- &two_pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::PointerDown { action_index: 1 },
+ pointer_properties: &two_pointer_properties,
+ ..BASE_EVENT
+ })
.is_ok());
// MOVE event with 1 pointer missing (the pointer with id = 1). It should be rejected
assert!(verifier
- .process_movement(
- DeviceId(1),
- Source::Touchscreen,
- input_bindgen::AMOTION_EVENT_ACTION_MOVE,
- &pointer_properties,
- MotionFlags::empty(),
- )
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Move,
+ pointer_properties: &pointer_properties,
+ ..BASE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn correct_button_press() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+ button_state: MotionButton::Primary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ }
+
+ #[test]
+ fn button_press_without_action_button() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::empty() },
+ button_state: MotionButton::empty(),
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn button_press_with_multiple_action_buttons() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress {
+ action_button: MotionButton::Back | MotionButton::Forward
+ },
+ button_state: MotionButton::Back | MotionButton::Forward,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn button_press_without_action_button_in_state() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+ button_state: MotionButton::empty(),
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn button_release_with_action_button_in_state() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+ button_state: MotionButton::Primary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonRelease { action_button: MotionButton::Primary },
+ button_state: MotionButton::Primary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn nonbutton_action_with_button_state_change() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::HoverEnter,
+ button_state: MotionButton::empty(),
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::HoverMove,
+ button_state: MotionButton::Back,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn nonbutton_action_missing_button_state() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::HoverEnter,
+ button_state: MotionButton::empty(),
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Back },
+ button_state: MotionButton::Back,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::HoverMove,
+ button_state: MotionButton::empty(),
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn up_without_button_release() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Down,
+ button_state: MotionButton::Primary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+ button_state: MotionButton::Primary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ // This UP event shouldn't change the button state; a BUTTON_RELEASE before it should.
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Up,
+ button_state: MotionButton::empty(),
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn button_press_for_already_pressed_button() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Back },
+ button_state: MotionButton::Back,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Back },
+ button_state: MotionButton::Back,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn button_release_for_unpressed_button() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonRelease { action_button: MotionButton::Back },
+ button_state: MotionButton::empty(),
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn correct_multiple_button_presses_without_down() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Back },
+ button_state: MotionButton::Back,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Forward },
+ button_state: MotionButton::Back | MotionButton::Forward,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ }
+
+ #[test]
+ fn correct_down_with_button_press() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Down,
+ button_state: MotionButton::Primary | MotionButton::Secondary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+ button_state: MotionButton::Primary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Secondary },
+ button_state: MotionButton::Primary | MotionButton::Secondary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ // Also check that the MOVE afterwards is OK, as that's where errors would be raised if not
+ // enough BUTTON_PRESSes were sent.
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Move,
+ button_state: MotionButton::Primary | MotionButton::Secondary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ }
+
+ #[test]
+ fn down_with_button_state_change_not_followed_by_button_press() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Down,
+ button_state: MotionButton::Primary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ // The DOWN event itself is OK, but it needs to be immediately followed by a BUTTON_PRESS.
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Move,
+ button_state: MotionButton::Primary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn down_with_button_state_change_not_followed_by_enough_button_presses() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Down,
+ button_state: MotionButton::Primary | MotionButton::Secondary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ // The DOWN event itself is OK, but it needs to be immediately followed by two
+ // BUTTON_PRESSes, one for each button.
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Primary },
+ button_state: MotionButton::Primary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Move,
+ button_state: MotionButton::Primary,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_err());
+ }
+
+ #[test]
+ fn down_missing_already_pressed_button() {
+ let mut verifier =
+ InputVerifier::new("Test", /*should_log*/ false, /*verify_buttons*/ true);
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::ButtonPress { action_button: MotionButton::Back },
+ button_state: MotionButton::Back,
+ ..BASE_MOUSE_EVENT
+ })
+ .is_ok());
+ assert!(verifier
+ .process_movement(NotifyMotionArgs {
+ action: MotionAction::Down,
+ button_state: MotionButton::empty(),
+ ..BASE_MOUSE_EVENT
+ })
.is_err());
}
}
diff --git a/libs/input/rust/keyboard_classification_config.rs b/libs/input/rust/keyboard_classification_config.rs
index ab74efb674..26a8d8f4b8 100644
--- a/libs/input/rust/keyboard_classification_config.rs
+++ b/libs/input/rust/keyboard_classification_config.rs
@@ -20,6 +20,15 @@ use crate::input::KeyboardType;
// key events at all. (Requires setup allowing InputDevice to dynamically add/remove
// KeyboardInputMapper based on blocklist and KeyEvents in case a KeyboardType::None device ends
// up producing a key event)
+
+/// This list pre-classifies a device into Alphabetic/Non-Alphabetic keyboard and tells us whether
+/// further re-classification should be allowed or not (using is_finalized value).
+/// This list DOES NOT change the source of the device or change the input mappers associated with
+/// the device. It only changes the "KeyboardType" classification. This list should be primarily
+/// used to pre-classify devices that are NOT keyboards(like mice, game pads, etc.) but generate
+/// evdev nodes that say that they are alphabetic keyboards.
+///
+/// NOTE: Pls keep the list sorted by vendor id and product id for easy searching.
pub static CLASSIFIED_DEVICES: &[(
/* vendorId */ u16,
/* productId */ u16,
@@ -96,6 +105,8 @@ pub static CLASSIFIED_DEVICES: &[(
(0x056e, 0x0159, KeyboardType::NonAlphabetic, true),
// Zebra LS2208 barcode scanner
(0x05e0, 0x1200, KeyboardType::NonAlphabetic, true),
+ // Glorious O2 Wireless
+ (0x093a, 0x822d, KeyboardType::NonAlphabetic, true),
// RDing FootSwitch1F1
(0x0c45, 0x7403, KeyboardType::NonAlphabetic, true),
// SteelSeries Sensei RAW Frost Blue
@@ -108,6 +119,8 @@ pub static CLASSIFIED_DEVICES: &[(
(0x1050, 0x0010, KeyboardType::NonAlphabetic, true),
// Yubico.com Yubikey 4 OTP+U2F+CCID
(0x1050, 0x0407, KeyboardType::NonAlphabetic, true),
+ // Razer DeathAdder Essential
+ (0x1532, 0x0098, KeyboardType::NonAlphabetic, true),
// Lenovo USB-C Wired Compact Mouse
(0x17ef, 0x6123, KeyboardType::NonAlphabetic, true),
// Corsair Katar Pro Wireless (USB dongle)
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 4f4ea8568b..ee999f7666 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -24,10 +24,10 @@ mod keyboard_classifier;
pub use data_store::{DataStore, DefaultFileReaderWriter};
pub use input::{
- DeviceClass, DeviceId, InputDevice, KeyboardType, ModifierState, MotionAction, MotionFlags,
- Source,
+ DeviceClass, DeviceId, InputDevice, KeyboardType, ModifierState, MotionAction, MotionButton,
+ MotionFlags, Source,
};
-pub use input_verifier::InputVerifier;
+pub use input_verifier::{InputVerifier, NotifyMotionArgs};
pub use keyboard_classifier::KeyboardClassifier;
#[cxx::bridge(namespace = "android::input")]
@@ -57,14 +57,17 @@ mod ffi {
/// ```
type InputVerifier;
#[cxx_name = create]
- fn create_input_verifier(name: String) -> Box<InputVerifier>;
+ fn create_input_verifier(name: String, verify_buttons: bool) -> Box<InputVerifier>;
+ #[allow(clippy::too_many_arguments)]
fn process_movement(
verifier: &mut InputVerifier,
device_id: i32,
source: u32,
action: u32,
+ action_button: u32,
pointer_properties: &[RustPointerProperties],
flags: u32,
+ button_state: u32,
) -> String;
fn reset_device(verifier: &mut InputVerifier, device_id: i32);
}
@@ -115,33 +118,67 @@ mod ffi {
use crate::ffi::{RustInputDeviceIdentifier, RustPointerProperties};
-fn create_input_verifier(name: String) -> Box<InputVerifier> {
- Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
+fn create_input_verifier(name: String, verify_buttons: bool) -> Box<InputVerifier> {
+ Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents"), verify_buttons))
}
+#[allow(clippy::too_many_arguments)]
fn process_movement(
verifier: &mut InputVerifier,
device_id: i32,
source: u32,
action: u32,
+ action_button: u32,
pointer_properties: &[RustPointerProperties],
flags: u32,
+ button_state: u32,
) -> String {
- let motion_flags = MotionFlags::from_bits(flags);
- if motion_flags.is_none() {
+ let Some(converted_source) = Source::from_bits(source) else {
+ panic!(
+ "The conversion of source 0x{source:08x} failed, please check if some sources have not \
+ been added to Source."
+ );
+ };
+ let Some(motion_flags) = MotionFlags::from_bits(flags) else {
panic!(
"The conversion of flags 0x{:08x} failed, please check if some flags have not been \
added to MotionFlags.",
flags
);
+ };
+ let Some(motion_action_button) = MotionButton::from_bits(action_button) else {
+ panic!(
+ "The conversion of action button 0x{action_button:08x} failed, please check if some \
+ buttons need to be added to MotionButton."
+ );
+ };
+ let Some(motion_button_state) = MotionButton::from_bits(button_state) else {
+ panic!(
+ "The conversion of button state 0x{button_state:08x} failed, please check if some \
+ buttons need to be added to MotionButton."
+ );
+ };
+ let motion_action = MotionAction::from_code(action, motion_action_button);
+ if motion_action_button != MotionButton::empty() {
+ match motion_action {
+ MotionAction::ButtonPress { action_button: _ }
+ | MotionAction::ButtonRelease { action_button: _ } => {}
+ _ => {
+ return format!(
+ "Invalid {motion_action} event: has action button {motion_action_button:?} but \
+ is not a button action"
+ );
+ }
+ }
}
- let result = verifier.process_movement(
- DeviceId(device_id),
- Source::from_bits(source).unwrap(),
- action,
+ let result = verifier.process_movement(NotifyMotionArgs {
+ device_id: DeviceId(device_id),
+ source: converted_source,
+ action: motion_action,
pointer_properties,
- motion_flags.unwrap(),
- );
+ flags: motion_flags,
+ button_state: motion_button_state,
+ });
match result {
Ok(()) => "".to_string(),
Err(e) => e,
@@ -208,3 +245,44 @@ fn process_key(
}
classifier.process_key(DeviceId(device_id), evdev_code, modifier_state.unwrap());
}
+
+#[cfg(test)]
+mod tests {
+ use crate::create_input_verifier;
+ use crate::process_movement;
+ use crate::RustPointerProperties;
+
+ const BASE_POINTER_PROPERTIES: [RustPointerProperties; 1] = [RustPointerProperties { id: 0 }];
+
+ #[test]
+ fn verify_nonbutton_action_with_action_button() {
+ let mut verifier = create_input_verifier("Test".to_string(), /*verify_buttons*/ true);
+ assert!(process_movement(
+ &mut verifier,
+ 1,
+ input_bindgen::AINPUT_SOURCE_MOUSE,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ input_bindgen::AMOTION_EVENT_BUTTON_PRIMARY,
+ &BASE_POINTER_PROPERTIES,
+ 0,
+ 0,
+ )
+ .contains("button action"));
+ }
+
+ #[test]
+ fn verify_nonbutton_action_with_action_button_and_button_state() {
+ let mut verifier = create_input_verifier("Test".to_string(), /*verify_buttons*/ true);
+ assert!(process_movement(
+ &mut verifier,
+ 1,
+ input_bindgen::AINPUT_SOURCE_MOUSE,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ input_bindgen::AMOTION_EVENT_BUTTON_PRIMARY,
+ &BASE_POINTER_PROPERTIES,
+ 0,
+ input_bindgen::AMOTION_EVENT_BUTTON_PRIMARY,
+ )
+ .contains("button action"));
+ }
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index e04236b9f5..968fa5fc1a 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -16,14 +16,17 @@ cc_test {
"BlockingQueue_test.cpp",
"IdGenerator_test.cpp",
"InputChannel_test.cpp",
+ "InputConsumerFilteredResampling_test.cpp",
+ "InputConsumerResampling_test.cpp",
"InputConsumer_test.cpp",
"InputDevice_test.cpp",
"InputEvent_test.cpp",
- "InputPublisherAndConsumer_test.cpp",
"InputPublisherAndConsumerNoResampling_test.cpp",
+ "InputPublisherAndConsumer_test.cpp",
"InputVerifier_test.cpp",
- "MotionPredictor_test.cpp",
"MotionPredictorMetricsManager_test.cpp",
+ "MotionPredictor_test.cpp",
+ "OneEuroFilter_test.cpp",
"Resampler_test.cpp",
"RingBuffer_test.cpp",
"TestInputChannel.cpp",
@@ -50,33 +53,41 @@ cc_test {
],
cflags: [
"-Wall",
- "-Wextra",
"-Werror",
+ "-Wextra",
"-Wno-unused-parameter",
],
sanitize: {
+ address: true,
hwaddress: true,
undefined: true,
all_undefined: true,
diag: {
+ cfi: true,
+ integer_overflow: true,
+ memtag_heap: true,
undefined: true,
+ misc_undefined: [
+ "all",
+ "bounds",
+ ],
},
},
shared_libs: [
+ "libPlatformProperties",
"libaconfig_storage_read_api_cc",
"libbase",
"libbinder",
"libcutils",
"liblog",
- "libPlatformProperties",
"libstatslog",
"libtinyxml2",
"libutils",
"server_configurable_flags",
],
data: [
- "data/*",
":motion_predictor_model",
+ "data/*",
],
test_options: {
unit_test: true,
@@ -89,11 +100,6 @@ cc_test {
"libstatssocket_lazy",
],
},
- host: {
- sanitize: {
- address: true,
- },
- },
},
native_coverage: false,
}
@@ -111,10 +117,10 @@ cc_library_static {
"-Wextra",
],
shared_libs: [
- "libinput",
+ "libbase",
+ "libbinder",
"libcutils",
+ "libinput",
"libutils",
- "libbinder",
- "libbase",
],
}
diff --git a/libs/input/tests/InputConsumerFilteredResampling_test.cpp b/libs/input/tests/InputConsumerFilteredResampling_test.cpp
new file mode 100644
index 0000000000..757cd18a38
--- /dev/null
+++ b/libs/input/tests/InputConsumerFilteredResampling_test.cpp
@@ -0,0 +1,218 @@
+/**
+ * Copyright 2024 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 <input/InputConsumerNoResampling.h>
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <queue>
+
+#include <TestEventMatchers.h>
+#include <TestInputChannel.h>
+#include <android-base/logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <input/InputEventBuilders.h>
+#include <input/Resampler.h>
+#include <utils/Looper.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace {
+
+using std::chrono::nanoseconds;
+
+using ::testing::AllOf;
+using ::testing::Matcher;
+
+const int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+const int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+
+struct Pointer {
+ int32_t id{0};
+ ToolType toolType{ToolType::FINGER};
+ float x{0.0f};
+ float y{0.0f};
+ bool isResampled{false};
+
+ PointerBuilder asPointerBuilder() const {
+ return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled);
+ }
+};
+
+} // namespace
+
+class InputConsumerFilteredResamplingTest : public ::testing::Test, public InputConsumerCallbacks {
+protected:
+ InputConsumerFilteredResamplingTest()
+ : mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")},
+ mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} {
+ Looper::setForThread(mLooper);
+ mConsumer = std::make_unique<
+ InputConsumerNoResampling>(mClientTestChannel, mLooper, *this, []() {
+ return std::make_unique<FilteredLegacyResampler>(/*minCutoffFreq=*/4.7, /*beta=*/0.01);
+ });
+ }
+
+ void invokeLooperCallback() const {
+ sp<LooperCallback> callback;
+ ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr,
+ /*events=*/nullptr, &callback, /*data=*/nullptr));
+ ASSERT_NE(callback, nullptr);
+ callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr);
+ }
+
+ void assertOnBatchedInputEventPendingWasCalled() {
+ ASSERT_GT(mOnBatchedInputEventPendingInvocationCount, 0UL)
+ << "onBatchedInputEventPending was not called";
+ --mOnBatchedInputEventPendingInvocationCount;
+ }
+
+ void assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher) {
+ ASSERT_TRUE(!mMotionEvents.empty()) << "No motion events were received";
+ std::unique_ptr<MotionEvent> motionEvent = std::move(mMotionEvents.front());
+ mMotionEvents.pop();
+ ASSERT_NE(motionEvent, nullptr) << "The consumed motion event must not be nullptr";
+ EXPECT_THAT(*motionEvent, matcher);
+ }
+
+ InputMessage nextPointerMessage(nanoseconds eventTime, int32_t action, const Pointer& pointer);
+
+ std::shared_ptr<TestInputChannel> mClientTestChannel;
+ sp<Looper> mLooper;
+ std::unique_ptr<InputConsumerNoResampling> mConsumer;
+
+ // Batched input events
+ std::queue<std::unique_ptr<KeyEvent>> mKeyEvents;
+ std::queue<std::unique_ptr<MotionEvent>> mMotionEvents;
+ std::queue<std::unique_ptr<FocusEvent>> mFocusEvents;
+ std::queue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
+ std::queue<std::unique_ptr<DragEvent>> mDragEvents;
+ std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
+
+private:
+ // InputConsumer callbacks
+ void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
+ mKeyEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
+ }
+
+ void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override {
+ mMotionEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
+ }
+
+ void onBatchedInputEventPending(int32_t pendingBatchSource) override {
+ if (!mConsumer->probablyHasInput()) {
+ ADD_FAILURE() << "Should deterministically have input because there is a batch";
+ }
+ ++mOnBatchedInputEventPendingInvocationCount;
+ }
+
+ void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
+ mFocusEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
+ }
+
+ void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override {
+ mCaptureEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
+ }
+
+ void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override {
+ mDragEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
+ }
+
+ void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override {
+ mTouchModeEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
+ }
+
+ uint32_t mLastSeq{0};
+ size_t mOnBatchedInputEventPendingInvocationCount{0};
+};
+
+InputMessage InputConsumerFilteredResamplingTest::nextPointerMessage(nanoseconds eventTime,
+ int32_t action,
+ const Pointer& pointer) {
+ ++mLastSeq;
+ return InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq}
+ .eventTime(eventTime.count())
+ .source(AINPUT_SOURCE_TOUCHSCREEN)
+ .action(action)
+ .pointer(pointer.asPointerBuilder())
+ .build();
+}
+
+TEST_F(InputConsumerFilteredResamplingTest, NeighboringTimestampsDoNotResultInZeroDivision) {
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage(0ms, ACTION_DOWN, Pointer{.x = 0.0f, .y = 0.0f}));
+
+ invokeLooperCallback();
+
+ assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithSampleCount(1)));
+
+ const std::chrono::nanoseconds initialTime{56'821'700'000'000};
+
+ mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 4'929'000ns, ACTION_MOVE,
+ Pointer{.x = 1.0f, .y = 1.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 9'352'000ns, ACTION_MOVE,
+ Pointer{.x = 2.0f, .y = 2.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 14'531'000ns, ACTION_MOVE,
+ Pointer{.x = 3.0f, .y = 3.0f}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(initialTime.count() + 18'849'395 /*ns*/);
+
+ assertOnBatchedInputEventPendingWasCalled();
+ // Three samples are expected. The first two of the batch, and the resampled one. The
+ // coordinates of the resampled sample are hardcoded because the matcher requires them. However,
+ // the primary intention here is to check that the last sample is resampled.
+ assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithSampleCount(3),
+ WithSample(/*sampleIndex=*/2,
+ Sample{initialTime + 13'849'395ns,
+ {PointerArgs{.x = 1.3286f,
+ .y = 1.3286f,
+ .isResampled = true}}})));
+
+ mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 20'363'000ns, ACTION_MOVE,
+ Pointer{.x = 4.0f, .y = 4.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 25'745'000ns, ACTION_MOVE,
+ Pointer{.x = 5.0f, .y = 5.0f}));
+ // This sample is part of the stream of messages, but should not be consumed because its
+ // timestamp is greater than the ajusted frame time.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(initialTime + 31'337'000ns, ACTION_MOVE,
+ Pointer{.x = 6.0f, .y = 6.0f}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(initialTime.count() + 35'516'062 /*ns*/);
+
+ assertOnBatchedInputEventPendingWasCalled();
+ // Four samples are expected because the last sample of the previous batch was not consumed.
+ assertReceivedMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithSampleCount(4)));
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/5, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/6, /*handled=*/true);
+}
+
+} // namespace android
diff --git a/libs/input/tests/InputConsumerResampling_test.cpp b/libs/input/tests/InputConsumerResampling_test.cpp
new file mode 100644
index 0000000000..97688a83ae
--- /dev/null
+++ b/libs/input/tests/InputConsumerResampling_test.cpp
@@ -0,0 +1,744 @@
+/*
+ * Copyright (C) 2024 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 <input/InputConsumerNoResampling.h>
+
+#include <chrono>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <TestEventMatchers.h>
+#include <TestInputChannel.h>
+#include <attestation/HmacKeyManager.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/BlockingQueue.h>
+#include <input/InputEventBuilders.h>
+#include <input/Resampler.h>
+#include <utils/Looper.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace {
+
+using std::chrono::nanoseconds;
+using namespace std::chrono_literals;
+
+const std::chrono::milliseconds RESAMPLE_LATENCY{5};
+
+struct Pointer {
+ int32_t id{0};
+ float x{0.0f};
+ float y{0.0f};
+ ToolType toolType{ToolType::FINGER};
+ bool isResampled{false};
+
+ PointerBuilder asPointerBuilder() const {
+ return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled);
+ }
+};
+
+struct InputEventEntry {
+ std::chrono::nanoseconds eventTime{0};
+ std::vector<Pointer> pointers{};
+ int32_t action{-1};
+};
+
+} // namespace
+
+class InputConsumerResamplingTest : public ::testing::Test, public InputConsumerCallbacks {
+protected:
+ InputConsumerResamplingTest()
+ : mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")},
+ mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} {
+ Looper::setForThread(mLooper);
+ mConsumer = std::make_unique<
+ InputConsumerNoResampling>(mClientTestChannel, mLooper, *this,
+ []() { return std::make_unique<LegacyResampler>(); });
+ }
+
+ void invokeLooperCallback() const {
+ sp<LooperCallback> callback;
+ ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr,
+ /*events=*/nullptr, &callback, /*data=*/nullptr));
+ ASSERT_NE(callback, nullptr);
+ callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr);
+ }
+
+ InputMessage nextPointerMessage(const InputEventEntry& entry);
+
+ void assertReceivedMotionEvent(const std::vector<InputEventEntry>& expectedEntries);
+
+ std::shared_ptr<TestInputChannel> mClientTestChannel;
+ sp<Looper> mLooper;
+ std::unique_ptr<InputConsumerNoResampling> mConsumer;
+
+ BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents;
+ BlockingQueue<std::unique_ptr<MotionEvent>> mMotionEvents;
+ BlockingQueue<std::unique_ptr<FocusEvent>> mFocusEvents;
+ BlockingQueue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
+ BlockingQueue<std::unique_ptr<DragEvent>> mDragEvents;
+ BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
+
+private:
+ uint32_t mLastSeq{0};
+ size_t mOnBatchedInputEventPendingInvocationCount{0};
+
+ // InputConsumerCallbacks interface
+ void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
+ mKeyEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ }
+ void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override {
+ mMotionEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ }
+ void onBatchedInputEventPending(int32_t pendingBatchSource) override {
+ if (!mConsumer->probablyHasInput()) {
+ ADD_FAILURE() << "should deterministically have input because there is a batch";
+ }
+ ++mOnBatchedInputEventPendingInvocationCount;
+ }
+ void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
+ mFocusEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ }
+ void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override {
+ mCaptureEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ }
+ void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override {
+ mDragEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ }
+ void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override {
+ mTouchModeEvents.push(std::move(event));
+ mConsumer->finishInputEvent(seq, true);
+ }
+};
+
+InputMessage InputConsumerResamplingTest::nextPointerMessage(const InputEventEntry& entry) {
+ ++mLastSeq;
+ InputMessageBuilder messageBuilder = InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq}
+ .eventTime(entry.eventTime.count())
+ .deviceId(1)
+ .action(entry.action)
+ .downTime(0);
+ for (const Pointer& pointer : entry.pointers) {
+ messageBuilder.pointer(pointer.asPointerBuilder());
+ }
+ return messageBuilder.build();
+}
+
+void InputConsumerResamplingTest::assertReceivedMotionEvent(
+ const std::vector<InputEventEntry>& expectedEntries) {
+ std::unique_ptr<MotionEvent> motionEvent = mMotionEvents.pop();
+ ASSERT_NE(motionEvent, nullptr);
+
+ ASSERT_EQ(motionEvent->getHistorySize() + 1, expectedEntries.size());
+
+ for (size_t sampleIndex = 0; sampleIndex < expectedEntries.size(); ++sampleIndex) {
+ SCOPED_TRACE("sampleIndex: " + std::to_string(sampleIndex));
+ const InputEventEntry& expectedEntry = expectedEntries[sampleIndex];
+ EXPECT_EQ(motionEvent->getHistoricalEventTime(sampleIndex),
+ expectedEntry.eventTime.count());
+ EXPECT_EQ(motionEvent->getPointerCount(), expectedEntry.pointers.size());
+ EXPECT_EQ(motionEvent->getAction(), expectedEntry.action);
+
+ for (size_t pointerIndex = 0; pointerIndex < expectedEntry.pointers.size();
+ ++pointerIndex) {
+ SCOPED_TRACE("pointerIndex: " + std::to_string(pointerIndex));
+ ssize_t eventPointerIndex =
+ motionEvent->findPointerIndex(expectedEntry.pointers[pointerIndex].id);
+ EXPECT_EQ(motionEvent->getHistoricalRawX(eventPointerIndex, sampleIndex),
+ expectedEntry.pointers[pointerIndex].x);
+ EXPECT_EQ(motionEvent->getHistoricalRawY(eventPointerIndex, sampleIndex),
+ expectedEntry.pointers[pointerIndex].y);
+ EXPECT_EQ(motionEvent->getHistoricalX(eventPointerIndex, sampleIndex),
+ expectedEntry.pointers[pointerIndex].x);
+ EXPECT_EQ(motionEvent->getHistoricalY(eventPointerIndex, sampleIndex),
+ expectedEntry.pointers[pointerIndex].y);
+ EXPECT_EQ(motionEvent->isResampled(pointerIndex, sampleIndex),
+ expectedEntry.pointers[pointerIndex].isResampled);
+ }
+ }
+}
+
+/**
+ * Timeline
+ * ---------+------------------+------------------+--------+-----------------+----------------------
+ * 0 ms 10 ms 20 ms 25 ms 35 ms
+ * ACTION_DOWN ACTION_MOVE ACTION_MOVE ^ ^
+ * | |
+ * resampled value |
+ * frameTime
+ * Typically, the prediction is made for time frameTime - RESAMPLE_LATENCY, or 30 ms in this case,
+ * where RESAMPLE_LATENCY equals 5 milliseconds. However, that would be 10 ms later than the last
+ * real sample (which came in at 20 ms). Therefore, the resampling should happen at 20 ms +
+ * RESAMPLE_MAX_PREDICTION = 28 ms, where RESAMPLE_MAX_PREDICTION equals 8 milliseconds. In this
+ * situation, though, resample time is further limited by taking half of the difference between the
+ * last two real events, which would put this time at: 20 ms + (20 ms - 10 ms) / 2 = 25 ms.
+ */
+TEST_F(InputConsumerResamplingTest, EventIsResampled) {
+ // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+ // InputEvent with a single action.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent({InputEventEntry{0ms,
+ {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+ assertReceivedMotionEvent(
+ {InputEventEntry{10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{25ms,
+ {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Same as above test, but use pointer id=1 instead of 0 to make sure that system does not
+ * have these hardcoded.
+ */
+TEST_F(InputConsumerResamplingTest, EventIsResampledWithDifferentId) {
+ // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+ // InputEvent with a single action.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms, {Pointer{.id = 1, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent({InputEventEntry{0ms,
+ {Pointer{.id = 1, .x = 10.0f, .y = 20.0f}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {10ms, {Pointer{.id = 1, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {20ms, {Pointer{.id = 1, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+ assertReceivedMotionEvent(
+ {InputEventEntry{10ms,
+ {Pointer{.id = 1, .x = 20.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{20ms,
+ {Pointer{.id = 1, .x = 30.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{25ms,
+ {Pointer{.id = 1, .x = 35.0f, .y = 30.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Stylus pointer coordinates are resampled.
+ */
+TEST_F(InputConsumerResamplingTest, StylusEventIsResampled) {
+ // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+ // InputEvent with a single action.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms,
+ {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::STYLUS}},
+ AMOTION_EVENT_ACTION_DOWN}));
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent({InputEventEntry{0ms,
+ {Pointer{.id = 0,
+ .x = 10.0f,
+ .y = 20.0f,
+ .toolType = ToolType::STYLUS}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::STYLUS}},
+ AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::STYLUS}},
+ AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+ assertReceivedMotionEvent({InputEventEntry{10ms,
+ {Pointer{.id = 0,
+ .x = 20.0f,
+ .y = 30.0f,
+ .toolType = ToolType::STYLUS}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{20ms,
+ {Pointer{.id = 0,
+ .x = 30.0f,
+ .y = 30.0f,
+ .toolType = ToolType::STYLUS}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{25ms,
+ {Pointer{.id = 0,
+ .x = 35.0f,
+ .y = 30.0f,
+ .toolType = ToolType::STYLUS,
+ .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Mouse pointer coordinates are resampled.
+ */
+TEST_F(InputConsumerResamplingTest, MouseEventIsResampled) {
+ // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+ // InputEvent with a single action.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms,
+ {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::MOUSE}},
+ AMOTION_EVENT_ACTION_DOWN}));
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent({InputEventEntry{0ms,
+ {Pointer{.id = 0,
+ .x = 10.0f,
+ .y = 20.0f,
+ .toolType = ToolType::MOUSE}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::MOUSE}},
+ AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::MOUSE}},
+ AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+ assertReceivedMotionEvent({InputEventEntry{10ms,
+ {Pointer{.id = 0,
+ .x = 20.0f,
+ .y = 30.0f,
+ .toolType = ToolType::MOUSE}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{20ms,
+ {Pointer{.id = 0,
+ .x = 30.0f,
+ .y = 30.0f,
+ .toolType = ToolType::MOUSE}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{25ms,
+ {Pointer{.id = 0,
+ .x = 35.0f,
+ .y = 30.0f,
+ .toolType = ToolType::MOUSE,
+ .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Motion events with palm tool type are not resampled.
+ */
+TEST_F(InputConsumerResamplingTest, PalmEventIsNotResampled) {
+ // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+ // InputEvent with a single action.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms,
+ {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::PALM}},
+ AMOTION_EVENT_ACTION_DOWN}));
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent(
+ {InputEventEntry{0ms,
+ {Pointer{.id = 0, .x = 10.0f, .y = 20.0f, .toolType = ToolType::PALM}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::PALM}},
+ AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::PALM}},
+ AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+ assertReceivedMotionEvent(
+ {InputEventEntry{10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f, .toolType = ToolType::PALM}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f, .toolType = ToolType::PALM}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Event should not be resampled when sample time is equal to event time.
+ */
+TEST_F(InputConsumerResamplingTest, SampleTimeEqualsEventTime) {
+ // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+ // InputEvent with a single action.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent({InputEventEntry{0ms,
+ {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{20ms + RESAMPLE_LATENCY}.count());
+
+ // MotionEvent should not resampled because the resample time falls exactly on the existing
+ // event time.
+ assertReceivedMotionEvent({InputEventEntry{10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * Once we send a resampled value to the app, we should continue to send the last predicted value if
+ * a pointer does not move. Only real values are used to determine if a pointer does not move.
+ */
+TEST_F(InputConsumerResamplingTest, ResampledValueIsUsedForIdenticalCoordinates) {
+ // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+ // InputEvent with a single action.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent({InputEventEntry{0ms,
+ {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+ assertReceivedMotionEvent(
+ {InputEventEntry{10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{25ms,
+ {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ // Coordinate value 30 has been resampled to 35. When a new event comes in with value 30 again,
+ // the system should still report 35.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {40ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{45ms + RESAMPLE_LATENCY}.count());
+ // Original and resampled event should be both overwritten.
+ assertReceivedMotionEvent(
+ {InputEventEntry{40ms,
+ {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{45ms,
+ {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
+}
+
+TEST_F(InputConsumerResamplingTest, OldEventReceivedAfterResampleOccurs) {
+ // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+ // InputEvent with a single action.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent({InputEventEntry{0ms,
+ {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{35ms}.count());
+ assertReceivedMotionEvent(
+ {InputEventEntry{10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{25ms,
+ {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ // Above, the resampled event is at 25ms rather than at 30 ms = 35ms - RESAMPLE_LATENCY
+ // because we are further bound by how far we can extrapolate by the "last time delta".
+ // That's 50% of (20 ms - 10ms) => 5ms. So we can't predict more than 5 ms into the future
+ // from the event at 20ms, which is why the resampled event is at t = 25 ms.
+
+ // We resampled the event to 25 ms. Now, an older 'real' event comes in.
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {24ms, {Pointer{.id = 0, .x = 40.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{50ms}.count());
+ // Original and resampled event should be both overwritten.
+ assertReceivedMotionEvent(
+ {InputEventEntry{24ms,
+ {Pointer{.id = 0, .x = 35.0f, .y = 30.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{26ms,
+ {Pointer{.id = 0, .x = 45.0f, .y = 30.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
+}
+
+TEST_F(InputConsumerResamplingTest, DoNotResampleWhenFrameTimeIsNotAvailable) {
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent({InputEventEntry{0ms,
+ {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {10ms, {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {20ms, {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(std::nullopt);
+ assertReceivedMotionEvent({InputEventEntry{10ms,
+ {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{20ms,
+ {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+TEST_F(InputConsumerResamplingTest, TwoPointersAreResampledIndependently) {
+ // Full action for when a pointer with index=1 appears (some other pointer must already be
+ // present)
+ const int32_t actionPointer1Down =
+ AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+ // Full action for when a pointer with index=0 disappears (some other pointer must still remain)
+ const int32_t actionPointer0Up =
+ AMOTION_EVENT_ACTION_POINTER_UP + (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {0ms, {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+ mClientTestChannel->assertNoSentMessages();
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent({InputEventEntry{0ms,
+ {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}},
+ AMOTION_EVENT_ACTION_DOWN}});
+
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {10ms, {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{10ms + RESAMPLE_LATENCY}.count());
+ // Not resampled value because requestedFrameTime - RESAMPLE_LATENCY == eventTime
+ assertReceivedMotionEvent({InputEventEntry{10ms,
+ {Pointer{.id = 0, .x = 100.0f, .y = 100.0f}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ // Second pointer id=1 appears
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage({15ms,
+ {Pointer{.id = 0, .x = 100.0f, .y = 100.0f},
+ Pointer{.id = 1, .x = 500.0f, .y = 500.0f}},
+ actionPointer1Down}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{20ms + RESAMPLE_LATENCY}.count());
+ // Not resampled value because requestedFrameTime - RESAMPLE_LATENCY == eventTime.
+ assertReceivedMotionEvent({InputEventEntry{15ms,
+ {Pointer{.id = 0, .x = 100.0f, .y = 100.0f},
+ Pointer{.id = 1, .x = 500.0f, .y = 500.0f}},
+ actionPointer1Down}});
+
+ // Both pointers move
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage({30ms,
+ {Pointer{.id = 0, .x = 100.0f, .y = 100.0f},
+ Pointer{.id = 1, .x = 500.0f, .y = 500.0f}},
+ AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage({40ms,
+ {Pointer{.id = 0, .x = 120.0f, .y = 120.0f},
+ Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+ AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{45ms + RESAMPLE_LATENCY}.count());
+ assertReceivedMotionEvent(
+ {InputEventEntry{30ms,
+ {Pointer{.id = 0, .x = 100.0f, .y = 100.0f},
+ Pointer{.id = 1, .x = 500.0f, .y = 500.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{40ms,
+ {Pointer{.id = 0, .x = 120.0f, .y = 120.0f},
+ Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{45ms,
+ {Pointer{.id = 0, .x = 130.0f, .y = 130.0f, .isResampled = true},
+ Pointer{.id = 1, .x = 650.0f, .y = 650.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ // Both pointers move again
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage({60ms,
+ {Pointer{.id = 0, .x = 120.0f, .y = 120.0f},
+ Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+ AMOTION_EVENT_ACTION_MOVE}));
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage({70ms,
+ {Pointer{.id = 0, .x = 130.0f, .y = 130.0f},
+ Pointer{.id = 1, .x = 700.0f, .y = 700.0f}},
+ AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{75ms + RESAMPLE_LATENCY}.count());
+
+ /*
+ * The pointer id 0 at t = 60 should not be equal to 120 because the value was received twice,
+ * and resampled to 130. Therefore, if we reported 130, then we should continue to report it as
+ * such. Likewise, with pointer id 1.
+ */
+
+ // Not 120 because it matches a previous real event.
+ assertReceivedMotionEvent(
+ {InputEventEntry{60ms,
+ {Pointer{.id = 0, .x = 130.0f, .y = 130.0f, .isResampled = true},
+ Pointer{.id = 1, .x = 650.0f, .y = 650.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{70ms,
+ {Pointer{.id = 0, .x = 130.0f, .y = 130.0f},
+ Pointer{.id = 1, .x = 700.0f, .y = 700.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{75ms,
+ {Pointer{.id = 0, .x = 135.0f, .y = 135.0f, .isResampled = true},
+ Pointer{.id = 1, .x = 750.0f, .y = 750.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+
+ // First pointer id=0 leaves the screen
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage({80ms,
+ {Pointer{.id = 0, .x = 120.0f, .y = 120.0f},
+ Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+ actionPointer0Up}));
+
+ invokeLooperCallback();
+ // Not resampled event for ACTION_POINTER_UP
+ assertReceivedMotionEvent({InputEventEntry{80ms,
+ {Pointer{.id = 0, .x = 120.0f, .y = 120.0f},
+ Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+ actionPointer0Up}});
+
+ // Remaining pointer id=1 is still present, but doesn't move
+ mClientTestChannel->enqueueMessage(nextPointerMessage(
+ {90ms, {Pointer{.id = 1, .x = 600.0f, .y = 600.0f}}, AMOTION_EVENT_ACTION_MOVE}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(nanoseconds{100ms}.count());
+
+ /*
+ * The latest event with ACTION_MOVE was at t = 70 with value = 700. Thus, the resampled value
+ * is 700 + ((95 - 70)/(90 - 70))*(600 - 700) = 575.
+ */
+ assertReceivedMotionEvent(
+ {InputEventEntry{90ms,
+ {Pointer{.id = 1, .x = 600.0f, .y = 600.0f}},
+ AMOTION_EVENT_ACTION_MOVE},
+ InputEventEntry{95ms,
+ {Pointer{.id = 1, .x = 575.0f, .y = 575.0f, .isResampled = true}},
+ AMOTION_EVENT_ACTION_MOVE}});
+}
+
+} // namespace android
diff --git a/libs/input/tests/InputConsumer_test.cpp b/libs/input/tests/InputConsumer_test.cpp
index d708316236..226b892fdc 100644
--- a/libs/input/tests/InputConsumer_test.cpp
+++ b/libs/input/tests/InputConsumer_test.cpp
@@ -16,6 +16,9 @@
#include <input/InputConsumerNoResampling.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
#include <memory>
#include <optional>
@@ -25,7 +28,9 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <input/BlockingQueue.h>
+#include <input/Input.h>
#include <input/InputEventBuilders.h>
+#include <input/Resampler.h>
#include <utils/Looper.h>
#include <utils/StrongPointer.h>
@@ -37,8 +42,21 @@ using std::chrono::nanoseconds;
using ::testing::AllOf;
using ::testing::Matcher;
-using ::testing::Not;
+constexpr auto ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr auto ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+
+struct Pointer {
+ int32_t id{0};
+ ToolType toolType{ToolType::FINGER};
+ float x{0.0f};
+ float y{0.0f};
+ bool isResampled{false};
+
+ PointerBuilder asPointerBuilder() const {
+ return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled);
+ }
+};
} // namespace
class InputConsumerTest : public testing::Test, public InputConsumerCallbacks {
@@ -47,16 +65,25 @@ protected:
: mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")},
mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} {
Looper::setForThread(mLooper);
- mConsumer =
- std::make_unique<InputConsumerNoResampling>(mClientTestChannel, mLooper, *this,
- std::make_unique<LegacyResampler>());
+ mConsumer = std::make_unique<
+ InputConsumerNoResampling>(mClientTestChannel, mLooper, *this,
+ []() { return std::make_unique<LegacyResampler>(); });
}
- void invokeLooperCallback() const {
+ bool invokeLooperCallback() const {
sp<LooperCallback> callback;
- ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr,
- /*events=*/nullptr, &callback, /*data=*/nullptr));
+ const bool found =
+ mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr,
+ /*events=*/nullptr, &callback, /*data=*/nullptr);
+ if (!found) {
+ return false;
+ }
+ if (callback == nullptr) {
+ LOG(FATAL) << "Looper has the fd of interest, but the callback is null!";
+ return false;
+ }
callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr);
+ return true;
}
void assertOnBatchedInputEventPendingWasCalled() {
@@ -65,34 +92,52 @@ protected:
--mOnBatchedInputEventPendingInvocationCount;
}
- void assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher) {
- std::unique_ptr<MotionEvent> motionEvent = mMotionEvents.pop();
- ASSERT_NE(motionEvent, nullptr);
+ std::unique_ptr<MotionEvent> assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher) {
+ if (mMotionEvents.empty()) {
+ ADD_FAILURE() << "No motion events received";
+ return nullptr;
+ }
+ std::unique_ptr<MotionEvent> motionEvent = std::move(mMotionEvents.front());
+ mMotionEvents.pop();
+ if (motionEvent == nullptr) {
+ ADD_FAILURE() << "The consumed motion event should never be null";
+ return nullptr;
+ }
EXPECT_THAT(*motionEvent, matcher);
+ return motionEvent;
}
+ InputMessage nextPointerMessage(std::chrono::nanoseconds eventTime, DeviceId deviceId,
+ int32_t action, const Pointer& pointer);
+
std::shared_ptr<TestInputChannel> mClientTestChannel;
sp<Looper> mLooper;
std::unique_ptr<InputConsumerNoResampling> mConsumer;
- BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents;
- BlockingQueue<std::unique_ptr<MotionEvent>> mMotionEvents;
- BlockingQueue<std::unique_ptr<FocusEvent>> mFocusEvents;
- BlockingQueue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
- BlockingQueue<std::unique_ptr<DragEvent>> mDragEvents;
- BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
+ std::queue<std::unique_ptr<KeyEvent>> mKeyEvents;
+ std::queue<std::unique_ptr<MotionEvent>> mMotionEvents;
+ std::queue<std::unique_ptr<FocusEvent>> mFocusEvents;
+ std::queue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
+ std::queue<std::unique_ptr<DragEvent>> mDragEvents;
+ std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
+
+ // Whether or not to automatically call "finish" whenever a motion event is received.
+ bool mShouldFinishMotions{true};
private:
+ uint32_t mLastSeq{0};
size_t mOnBatchedInputEventPendingInvocationCount{0};
// InputConsumerCallbacks interface
void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
mKeyEvents.push(std::move(event));
- mConsumer->finishInputEvent(seq, true);
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
}
void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override {
mMotionEvents.push(std::move(event));
- mConsumer->finishInputEvent(seq, true);
+ if (mShouldFinishMotions) {
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
+ }
}
void onBatchedInputEventPending(int32_t pendingBatchSource) override {
if (!mConsumer->probablyHasInput()) {
@@ -102,34 +147,47 @@ private:
};
void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
mFocusEvents.push(std::move(event));
- mConsumer->finishInputEvent(seq, true);
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
};
void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override {
mCaptureEvents.push(std::move(event));
- mConsumer->finishInputEvent(seq, true);
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
};
void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override {
mDragEvents.push(std::move(event));
- mConsumer->finishInputEvent(seq, true);
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
}
void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override {
mTouchModeEvents.push(std::move(event));
- mConsumer->finishInputEvent(seq, true);
+ mConsumer->finishInputEvent(seq, /*handled=*/true);
};
};
+InputMessage InputConsumerTest::nextPointerMessage(std::chrono::nanoseconds eventTime,
+ DeviceId deviceId, int32_t action,
+ const Pointer& pointer) {
+ ++mLastSeq;
+ return InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq}
+ .eventTime(eventTime.count())
+ .deviceId(deviceId)
+ .source(AINPUT_SOURCE_TOUCHSCREEN)
+ .action(action)
+ .pointer(pointer.asPointerBuilder())
+ .build();
+}
+
TEST_F(InputConsumerTest, MessageStreamBatchedInMotionEvent) {
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
.eventTime(nanoseconds{0ms}.count())
- .action(AMOTION_EVENT_ACTION_DOWN)
+ .action(ACTION_DOWN)
.build());
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}
.eventTime(nanoseconds{5ms}.count())
- .action(AMOTION_EVENT_ACTION_MOVE)
+ .action(ACTION_MOVE)
.build());
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2}
.eventTime(nanoseconds{10ms}.count())
- .action(AMOTION_EVENT_ACTION_MOVE)
+ .action(ACTION_MOVE)
.build());
mClientTestChannel->assertNoSentMessages();
@@ -140,12 +198,12 @@ TEST_F(InputConsumerTest, MessageStreamBatchedInMotionEvent) {
mConsumer->consumeBatchedInputEvents(/*frameTime=*/std::nullopt);
- std::unique_ptr<MotionEvent> downMotionEvent = mMotionEvents.pop();
- ASSERT_NE(downMotionEvent, nullptr);
+ assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN));
- std::unique_ptr<MotionEvent> moveMotionEvent = mMotionEvents.pop();
+ std::unique_ptr<MotionEvent> moveMotionEvent =
+ assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE));
ASSERT_NE(moveMotionEvent, nullptr);
- EXPECT_EQ(moveMotionEvent->getHistorySize() + 1, 3UL);
+ EXPECT_EQ(moveMotionEvent->getHistorySize() + 1, 2UL);
mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
@@ -155,19 +213,19 @@ TEST_F(InputConsumerTest, MessageStreamBatchedInMotionEvent) {
TEST_F(InputConsumerTest, LastBatchedSampleIsLessThanResampleTime) {
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
.eventTime(nanoseconds{0ms}.count())
- .action(AMOTION_EVENT_ACTION_DOWN)
+ .action(ACTION_DOWN)
.build());
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}
.eventTime(nanoseconds{5ms}.count())
- .action(AMOTION_EVENT_ACTION_MOVE)
+ .action(ACTION_MOVE)
.build());
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2}
.eventTime(nanoseconds{10ms}.count())
- .action(AMOTION_EVENT_ACTION_MOVE)
+ .action(ACTION_MOVE)
.build());
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/3}
.eventTime(nanoseconds{15ms}.count())
- .action(AMOTION_EVENT_ACTION_MOVE)
+ .action(ACTION_MOVE)
.build());
mClientTestChannel->assertNoSentMessages();
@@ -178,56 +236,119 @@ TEST_F(InputConsumerTest, LastBatchedSampleIsLessThanResampleTime) {
mConsumer->consumeBatchedInputEvents(16'000'000 /*ns*/);
- std::unique_ptr<MotionEvent> downMotionEvent = mMotionEvents.pop();
- ASSERT_NE(downMotionEvent, nullptr);
+ assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN));
- std::unique_ptr<MotionEvent> moveMotionEvent = mMotionEvents.pop();
+ std::unique_ptr<MotionEvent> moveMotionEvent =
+ assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE));
ASSERT_NE(moveMotionEvent, nullptr);
const size_t numSamples = moveMotionEvent->getHistorySize() + 1;
EXPECT_LT(moveMotionEvent->getHistoricalEventTime(numSamples - 2),
moveMotionEvent->getEventTime());
- // Consume all remaining events before ending the test. Otherwise, the smart pointer that owns
- // consumer is set to null before destroying consumer. This leads to a member function call on a
- // null object.
- // TODO(b/332613662): Remove this workaround.
- mConsumer->consumeBatchedInputEvents(std::nullopt);
+ mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ // The event with seq=3 remains unconsumed, and therefore finish will not be called for it until
+ // after the consumer is destroyed.
+ mConsumer.reset();
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/false);
+ mClientTestChannel->assertNoSentMessages();
+}
+
+/**
+ * During normal operation, the user of InputConsumer (callbacks) is expected to call "finish"
+ * for each input event received in InputConsumerCallbacks.
+ * If the InputConsumer is destroyed, the events that were already sent to the callbacks will not
+ * be finished automatically.
+ */
+TEST_F(InputConsumerTest, UnhandledEventsNotFinishedInDestructor) {
+ mClientTestChannel->enqueueMessage(
+ InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}.action(ACTION_DOWN).build());
+ mClientTestChannel->enqueueMessage(
+ InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}.action(ACTION_MOVE).build());
+ mShouldFinishMotions = false;
+ invokeLooperCallback();
+ assertOnBatchedInputEventPendingWasCalled();
+ assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN));
+ mClientTestChannel->assertNoSentMessages();
+ // The "finishInputEvent" was not called by the InputConsumerCallbacks.
+ // Now, destroy the consumer and check that the "finish" was not called automatically for the
+ // DOWN event, but was called for the undelivered MOVE event.
+ mConsumer.reset();
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/false);
+ mClientTestChannel->assertNoSentMessages();
+}
- mClientTestChannel->assertFinishMessage(/*seq=*/0, true);
- mClientTestChannel->assertFinishMessage(/*seq=*/1, true);
- mClientTestChannel->assertFinishMessage(/*seq=*/2, true);
- mClientTestChannel->assertFinishMessage(/*seq=*/3, true);
+/**
+ * Check what happens when looper invokes callback after consumer has been destroyed.
+ * This reproduces a crash where the LooperEventCallback was added back to the Looper during
+ * destructor, thus allowing the looper callback to be invoked onto a null consumer object.
+ */
+TEST_F(InputConsumerTest, LooperCallbackInvokedAfterConsumerDestroyed) {
+ mClientTestChannel->enqueueMessage(
+ InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}.action(ACTION_DOWN).build());
+ mClientTestChannel->enqueueMessage(
+ InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}.action(ACTION_MOVE).build());
+ ASSERT_TRUE(invokeLooperCallback());
+ assertOnBatchedInputEventPendingWasCalled();
+ assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN));
+ mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
+
+ // Now, destroy the consumer and invoke the looper callback again after it's been destroyed.
+ mConsumer.reset();
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/false);
+ ASSERT_FALSE(invokeLooperCallback());
+}
+
+/**
+ * Send an event to the InputConsumer, but do not invoke "consumeBatchedInputEvents", thus leaving
+ * the input event unconsumed by the callbacks. Ensure that no crash occurs when the consumer is
+ * destroyed.
+ * This test is similar to the one above, but here we are calling "finish"
+ * automatically for any event received in the callbacks.
+ */
+TEST_F(InputConsumerTest, UnconsumedEventDoesNotCauseACrash) {
+ mClientTestChannel->enqueueMessage(
+ InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}.action(ACTION_DOWN).build());
+ invokeLooperCallback();
+ assertReceivedMotionEvent(WithMotionAction(ACTION_DOWN));
+ mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
+ mClientTestChannel->enqueueMessage(
+ InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}.action(ACTION_MOVE).build());
+ invokeLooperCallback();
+ mConsumer.reset();
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/false);
}
TEST_F(InputConsumerTest, BatchedEventsMultiDeviceConsumption) {
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
.deviceId(0)
- .action(AMOTION_EVENT_ACTION_DOWN)
+ .action(ACTION_DOWN)
.build());
invokeLooperCallback();
- assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+ assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(ACTION_DOWN)));
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}
.deviceId(0)
- .action(AMOTION_EVENT_ACTION_MOVE)
+ .action(ACTION_MOVE)
.build());
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2}
.deviceId(0)
- .action(AMOTION_EVENT_ACTION_MOVE)
+ .action(ACTION_MOVE)
.build());
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/3}
.deviceId(0)
- .action(AMOTION_EVENT_ACTION_MOVE)
+ .action(ACTION_MOVE)
.build());
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/4}
.deviceId(1)
- .action(AMOTION_EVENT_ACTION_DOWN)
+ .action(ACTION_DOWN)
.build());
invokeLooperCallback();
- assertReceivedMotionEvent(AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+ assertReceivedMotionEvent(AllOf(WithDeviceId(1), WithMotionAction(ACTION_DOWN)));
mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/5}
.deviceId(0)
@@ -235,8 +356,7 @@ TEST_F(InputConsumerTest, BatchedEventsMultiDeviceConsumption) {
.build());
invokeLooperCallback();
- assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
- Not(MotionEventIsResampled())));
+ assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(ACTION_MOVE)));
mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
@@ -244,4 +364,114 @@ TEST_F(InputConsumerTest, BatchedEventsMultiDeviceConsumption) {
mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
}
+
+/**
+ * The test supposes a 60Hz Vsync rate and a 200Hz input rate. The InputMessages are intertwined as
+ * in a real use cases. The test's two devices should be resampled independently. Moreover, the
+ * InputMessage stream layout for the test is:
+ *
+ * DOWN(0, 0ms)
+ * MOVE(0, 5ms)
+ * MOVE(0, 10ms)
+ * DOWN(1, 15ms)
+ *
+ * CONSUME(16ms)
+ *
+ * MOVE(1, 20ms)
+ * MOVE(1, 25ms)
+ * MOVE(0, 30ms)
+ *
+ * CONSUME(32ms)
+ *
+ * MOVE(0, 35ms)
+ * UP(1, 40ms)
+ * UP(0, 45ms)
+ *
+ * CONSUME(48ms)
+ *
+ * The first field is device ID, and the second field is event time.
+ */
+TEST_F(InputConsumerTest, MultiDeviceResampling) {
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage(0ms, /*deviceId=*/0, ACTION_DOWN, Pointer{.x = 0, .y = 0}));
+
+ mClientTestChannel->assertNoSentMessages();
+
+ invokeLooperCallback();
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(0), WithMotionAction(ACTION_DOWN), WithSampleCount(1)));
+
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage(5ms, /*deviceId=*/0, ACTION_MOVE, Pointer{.x = 1.0f, .y = 2.0f}));
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage(10ms, /*deviceId=*/0, ACTION_MOVE, Pointer{.x = 2.0f, .y = 4.0f}));
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage(15ms, /*deviceId=*/1, ACTION_DOWN, Pointer{.x = 10.0f, .y = 10.0f}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(16'000'000 /*ns*/);
+
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(1), WithMotionAction(ACTION_DOWN), WithSampleCount(1)));
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(0), WithMotionAction(ACTION_MOVE), WithSampleCount(3),
+ WithSample(/*sampleIndex=*/2,
+ Sample{11ms,
+ {PointerArgs{.x = 2.2f, .y = 4.4f, .isResampled = true}}})));
+
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage(20ms, /*deviceId=*/1, ACTION_MOVE, Pointer{.x = 11.0f, .y = 12.0f}));
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage(25ms, /*deviceId=*/1, ACTION_MOVE, Pointer{.x = 12.0f, .y = 14.0f}));
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage(30ms, /*deviceId=*/0, ACTION_MOVE, Pointer{.x = 5.0f, .y = 6.0f}));
+
+ invokeLooperCallback();
+ assertOnBatchedInputEventPendingWasCalled();
+ mConsumer->consumeBatchedInputEvents(32'000'000 /*ns*/);
+
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(1), WithMotionAction(ACTION_MOVE), WithSampleCount(3),
+ WithSample(/*sampleIndex=*/2,
+ Sample{27ms,
+ {PointerArgs{.x = 12.4f, .y = 14.8f, .isResampled = true}}})));
+
+ mClientTestChannel->enqueueMessage(
+ nextPointerMessage(35ms, /*deviceId=*/0, ACTION_MOVE, Pointer{.x = 8.0f, .y = 9.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(40ms, /*deviceId=*/1,
+ AMOTION_EVENT_ACTION_UP,
+ Pointer{.x = 12.0f, .y = 14.0f}));
+ mClientTestChannel->enqueueMessage(nextPointerMessage(45ms, /*deviceId=*/0,
+ AMOTION_EVENT_ACTION_UP,
+ Pointer{.x = 8.0f, .y = 9.0f}));
+
+ invokeLooperCallback();
+ mConsumer->consumeBatchedInputEvents(48'000'000 /*ns*/);
+
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSampleCount(1)));
+
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(0), WithMotionAction(ACTION_MOVE), WithSampleCount(3),
+ WithSample(/*sampleIndex=*/2,
+ Sample{37'500'000ns,
+ {PointerArgs{.x = 9.5f, .y = 10.5f, .isResampled = true}}})));
+
+ assertReceivedMotionEvent(
+ AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSampleCount(1)));
+
+ // The sequence order is based on the expected consumption. Each sequence number corresponds to
+ // one of the previously enqueued messages.
+ mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/5, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/6, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/9, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/7, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/8, /*handled=*/true);
+ mClientTestChannel->assertFinishMessage(/*seq=*/10, /*handled=*/true);
+}
+
} // namespace android
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
index 1210f711de..1dadae98e4 100644
--- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -14,15 +14,18 @@
* limitations under the License.
*/
+#include <TestEventMatchers.h>
#include <android-base/logging.h>
#include <attestation/HmacKeyManager.h>
#include <ftl/enum.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <input/BlockingQueue.h>
#include <input/InputConsumerNoResampling.h>
#include <input/InputTransport.h>
using android::base::Result;
+using ::testing::Matcher;
namespace android {
@@ -278,7 +281,7 @@ protected:
void SetUp() override {
std::unique_ptr<InputChannel> serverChannel;
status_t result =
- InputChannel::openInputChannelPair("channel name", serverChannel, mClientChannel);
+ InputChannel::openInputChannelPair("test channel", serverChannel, mClientChannel);
ASSERT_EQ(OK, result);
mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel));
@@ -316,6 +319,8 @@ protected:
protected:
// Interaction with the looper thread
+ void blockLooper();
+ void unblockLooper();
enum class LooperMessage : int {
CALL_PROBABLY_HAS_INPUT,
CREATE_CONSUMER,
@@ -336,6 +341,8 @@ protected:
// accessed on the test thread.
BlockingQueue<bool> mProbablyHasInputResponses;
+ std::unique_ptr<MotionEvent> assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher);
+
private:
sp<MessageHandler> mMessageHandler;
void handleMessage(const Message& message);
@@ -384,11 +391,45 @@ private:
};
};
+void InputPublisherAndConsumerNoResamplingTest::blockLooper() {
+ {
+ std::scoped_lock l(mLock);
+ mLooperMayProceed = false;
+ }
+ sendMessage(LooperMessage::BLOCK_LOOPER);
+ {
+ std::unique_lock l(mLock);
+ mNotifyLooperWaiting.wait(l, [this] { return mLooperIsBlocked; });
+ }
+}
+
+void InputPublisherAndConsumerNoResamplingTest::unblockLooper() {
+ {
+ std::scoped_lock l(mLock);
+ mLooperMayProceed = true;
+ }
+ mNotifyLooperMayProceed.notify_all();
+}
+
void InputPublisherAndConsumerNoResamplingTest::sendMessage(LooperMessage message) {
Message msg{ftl::to_underlying(message)};
mLooper->sendMessage(mMessageHandler, msg);
}
+std::unique_ptr<MotionEvent> InputPublisherAndConsumerNoResamplingTest::assertReceivedMotionEvent(
+ const Matcher<MotionEvent>& matcher) {
+ std::optional<std::unique_ptr<MotionEvent>> event = mMotionEvents.popWithTimeout(TIMEOUT);
+ if (!event) {
+ ADD_FAILURE() << "No event was received, but expected motion " << matcher;
+ return nullptr;
+ }
+ if (*event == nullptr) {
+ LOG(FATAL) << "Event was received, but it was null";
+ }
+ EXPECT_THAT(**event, matcher);
+ return std::move(*event);
+}
+
void InputPublisherAndConsumerNoResamplingTest::handleMessage(const Message& message) {
switch (static_cast<LooperMessage>(message.what)) {
case LooperMessage::CALL_PROBABLY_HAS_INPUT: {
@@ -572,8 +613,7 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeSinglePointerMu
const nsecs_t publishTimeOfDown = systemTime(SYSTEM_TIME_MONOTONIC);
publishMotionEvent(*mPublisher, argsDown);
- // Consume the DOWN event.
- ASSERT_TRUE(mMotionEvents.popWithTimeout(TIMEOUT).has_value());
+ assertReceivedMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
verifyFinishedSignal(*mPublisher, mSeq, publishTimeOfDown);
@@ -582,15 +622,7 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeSinglePointerMu
std::queue<uint32_t> publishedSequenceNumbers;
// Block Looper to increase the chance of batching events
- {
- std::scoped_lock l(mLock);
- mLooperMayProceed = false;
- }
- sendMessage(LooperMessage::BLOCK_LOOPER);
- {
- std::unique_lock l(mLock);
- mNotifyLooperWaiting.wait(l, [this] { return mLooperIsBlocked; });
- }
+ blockLooper();
uint32_t firstSampleId;
for (size_t i = 0; i < nSamples; ++i) {
@@ -611,21 +643,16 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeSinglePointerMu
std::vector<MotionEvent> singleSampledMotionEvents;
- // Unblock Looper
- {
- std::scoped_lock l(mLock);
- mLooperMayProceed = true;
- }
- mNotifyLooperMayProceed.notify_all();
+ unblockLooper();
// We have no control over the socket behavior, so the consumer can receive
// the motion as a batched event, or as a sequence of multiple single-sample MotionEvents (or a
// mix of those)
while (singleSampledMotionEvents.size() != nSamples) {
- const std::optional<std::unique_ptr<MotionEvent>> batchedMotionEvent =
- mMotionEvents.popWithTimeout(TIMEOUT);
+ const std::unique_ptr<MotionEvent> batchedMotionEvent =
+ assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE));
// The events received by these calls are never null
- std::vector<MotionEvent> splitMotionEvents = splitBatchedMotionEvent(**batchedMotionEvent);
+ std::vector<MotionEvent> splitMotionEvents = splitBatchedMotionEvent(*batchedMotionEvent);
singleSampledMotionEvents.insert(singleSampledMotionEvents.end(), splitMotionEvents.begin(),
splitMotionEvents.end());
}
@@ -681,10 +708,7 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeBatchedMotionMo
}
mNotifyLooperMayProceed.notify_all();
- std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
- ASSERT_TRUE(optMotion.has_value());
- std::unique_ptr<MotionEvent> motion = std::move(*optMotion);
- ASSERT_EQ(ACTION_MOVE, motion->getAction());
+ assertReceivedMotionEvent(WithMotionAction(ACTION_MOVE));
verifyFinishedSignal(*mPublisher, seq, publishTime);
}
@@ -696,9 +720,7 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionEvent(
nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
publishMotionEvent(*mPublisher, args);
- std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
- ASSERT_TRUE(optMotion.has_value());
- std::unique_ptr<MotionEvent> event = std::move(*optMotion);
+ std::unique_ptr<MotionEvent> event = assertReceivedMotionEvent(WithMotionAction(action));
verifyArgsEqualToEvent(args, *event);
@@ -796,6 +818,15 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeTouchModeEvent(
verifyFinishedSignal(*mPublisher, seq, publishTime);
}
+/**
+ * If the publisher has died, consumer should not crash when trying to send an outgoing message.
+ */
+TEST_F(InputPublisherAndConsumerNoResamplingTest, ConsumerWritesAfterPublisherDies) {
+ mPublisher.reset(); // The publisher has died
+ mReportTimelineArgs.emplace(/*inputEventId=*/10, /*gpuCompletedTime=*/20, /*presentTime=*/30);
+ sendMessage(LooperMessage::CALL_REPORT_TIMELINE);
+}
+
TEST_F(InputPublisherAndConsumerNoResamplingTest, SendTimeline) {
const int32_t inputEventId = 20;
const nsecs_t gpuCompletedTime = 30;
diff --git a/libs/input/tests/InputVerifier_test.cpp b/libs/input/tests/InputVerifier_test.cpp
index e2eb08096b..8e0d9068c1 100644
--- a/libs/input/tests/InputVerifier_test.cpp
+++ b/libs/input/tests/InputVerifier_test.cpp
@@ -14,9 +14,13 @@
* limitations under the License.
*/
+#include <android/input.h>
+#include <android-base/result.h>
#include <gtest/gtest.h>
+#include <input/Input.h>
#include <input/InputVerifier.h>
#include <string>
+#include <vector>
namespace android {
@@ -45,10 +49,10 @@ TEST(InputVerifierTest, ProcessSourceClassPointer) {
const Result<void> result =
verifier.processMovement(/*deviceId=*/0, AINPUT_SOURCE_CLASS_POINTER,
- AMOTION_EVENT_ACTION_DOWN,
+ AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0,
/*pointerCount=*/properties.size(), properties.data(),
- coords.data(), /*flags=*/0);
- ASSERT_TRUE(result.ok());
+ coords.data(), /*flags=*/0, /*buttonState=*/0);
+ ASSERT_RESULT_OK(result);
}
} // namespace android
diff --git a/libs/input/tests/OneEuroFilter_test.cpp b/libs/input/tests/OneEuroFilter_test.cpp
new file mode 100644
index 0000000000..8645508ea7
--- /dev/null
+++ b/libs/input/tests/OneEuroFilter_test.cpp
@@ -0,0 +1,137 @@
+/**
+ * Copyright 2024 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 <input/OneEuroFilter.h>
+
+#include <algorithm>
+#include <chrono>
+#include <cmath>
+#include <numeric>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <input/Input.h>
+
+namespace android {
+namespace {
+
+using namespace std::literals::chrono_literals;
+using std::chrono::duration;
+
+struct Sample {
+ duration<double> timestamp{};
+ double value{};
+
+ friend bool operator<(const Sample& lhs, const Sample& rhs) { return lhs.value < rhs.value; }
+};
+
+/**
+ * Generates a sinusoidal signal with the passed frequency and amplitude.
+ */
+std::vector<Sample> generateSinusoidalSignal(duration<double> signalDuration,
+ double samplingFrequency, double signalFrequency,
+ double amplitude) {
+ std::vector<Sample> signal;
+ const duration<double> samplingPeriod{1.0 / samplingFrequency};
+ for (duration<double> timestamp{0.0}; timestamp < signalDuration; timestamp += samplingPeriod) {
+ signal.push_back(
+ Sample{timestamp,
+ amplitude * std::sin(2.0 * M_PI * signalFrequency * timestamp.count())});
+ }
+ return signal;
+}
+
+double meanAbsoluteError(const std::vector<Sample>& filteredSignal,
+ const std::vector<Sample>& signal) {
+ if (filteredSignal.size() != signal.size()) {
+ ADD_FAILURE() << "filteredSignal and signal do not have equal number of samples";
+ return std::numeric_limits<double>::max();
+ }
+ std::vector<double> absoluteError;
+ for (size_t sampleIndex = 0; sampleIndex < signal.size(); ++sampleIndex) {
+ absoluteError.push_back(
+ std::abs(filteredSignal[sampleIndex].value - signal[sampleIndex].value));
+ }
+ if (absoluteError.empty()) {
+ ADD_FAILURE() << "Zero division. absoluteError is empty";
+ return std::numeric_limits<double>::max();
+ }
+ return std::accumulate(absoluteError.begin(), absoluteError.end(), 0.0) / absoluteError.size();
+}
+
+double maxAbsoluteAmplitude(const std::vector<Sample>& signal) {
+ if (signal.empty()) {
+ ADD_FAILURE() << "Max absolute value amplitude does not exist. Signal is empty";
+ return std::numeric_limits<double>::max();
+ }
+ std::vector<Sample> absoluteSignal;
+ for (const Sample& sample : signal) {
+ absoluteSignal.push_back(Sample{sample.timestamp, std::abs(sample.value)});
+ }
+ return std::max_element(absoluteSignal.begin(), absoluteSignal.end())->value;
+}
+
+} // namespace
+
+class OneEuroFilterTest : public ::testing::Test {
+protected:
+ // The constructor's parameters are the ones that Chromium's using. The tuning was based on a 60
+ // Hz sampling frequency. Refer to their one_euro_filter.h header for additional information
+ // about these parameters.
+ OneEuroFilterTest() : mFilter{/*minCutoffFreq=*/4.7, /*beta=*/0.01} {}
+
+ std::vector<Sample> filterSignal(const std::vector<Sample>& signal) {
+ std::vector<Sample> filteredSignal;
+ for (const Sample& sample : signal) {
+ filteredSignal.push_back(
+ Sample{sample.timestamp,
+ mFilter.filter(std::chrono::duration_cast<std::chrono::nanoseconds>(
+ sample.timestamp),
+ sample.value)});
+ }
+ return filteredSignal;
+ }
+
+ OneEuroFilter mFilter;
+};
+
+TEST_F(OneEuroFilterTest, PassLowFrequencySignal) {
+ const std::vector<Sample> signal =
+ generateSinusoidalSignal(1s, /*samplingFrequency=*/60, /*signalFrequency=*/1,
+ /*amplitude=*/1);
+
+ const std::vector<Sample> filteredSignal = filterSignal(signal);
+
+ // The reason behind using the mean absolute error as a metric is that, ideally, a low frequency
+ // filtered signal is expected to be almost identical to the raw one. Therefore, the error
+ // between them should be minimal. The constant is heuristically chosen.
+ EXPECT_LT(meanAbsoluteError(filteredSignal, signal), 0.25);
+}
+
+TEST_F(OneEuroFilterTest, RejectHighFrequencySignal) {
+ const std::vector<Sample> signal =
+ generateSinusoidalSignal(1s, /*samplingFrequency=*/60, /*signalFrequency=*/22.5,
+ /*amplitude=*/1);
+
+ const std::vector<Sample> filteredSignal = filterSignal(signal);
+
+ // The filtered signal should consist of values that are much closer to zero. The comparison
+ // constant is heuristically chosen.
+ EXPECT_LT(maxAbsoluteAmplitude(filteredSignal), 0.25);
+}
+
+} // namespace android
diff --git a/libs/input/tests/Resampler_test.cpp b/libs/input/tests/Resampler_test.cpp
index 26dee393c1..3162b77c85 100644
--- a/libs/input/tests/Resampler_test.cpp
+++ b/libs/input/tests/Resampler_test.cpp
@@ -87,7 +87,6 @@ InputSample::operator InputMessage() const {
struct InputStream {
std::vector<InputSample> samples{};
int32_t action{0};
- DeviceId deviceId{0};
/**
* Converts from InputStream to MotionEvent. Enables calling LegacyResampler methods only with
* the relevant data for tests.
@@ -100,8 +99,8 @@ InputStream::operator MotionEvent() const {
MotionEventBuilder motionEventBuilder =
MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER)
.downTime(0)
- .eventTime(static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count())
- .deviceId(deviceId);
+ .eventTime(
+ static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count());
for (const Pointer& pointer : firstSample.pointers) {
const PointerBuilder pointerBuilder =
PointerBuilder(pointer.id, pointer.toolType).x(pointer.x).y(pointer.y);
@@ -289,28 +288,6 @@ TEST_F(ResamplerTest, SinglePointerNotEnoughDataToResample) {
assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
}
-TEST_F(ResamplerTest, SinglePointerDifferentDeviceIdBetweenMotionEvents) {
- MotionEvent motionFromFirstDevice =
- InputStream{{InputSample{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
- InputSample{8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
- AMOTION_EVENT_ACTION_MOVE,
- .deviceId = 0};
-
- mResampler->resampleMotionEvent(10ms, motionFromFirstDevice, nullptr);
-
- MotionEvent motionFromSecondDevice =
- InputStream{{InputSample{11ms,
- {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
- AMOTION_EVENT_ACTION_MOVE,
- .deviceId = 1};
- const MotionEvent originalMotionEvent = motionFromSecondDevice;
-
- mResampler->resampleMotionEvent(12ms, motionFromSecondDevice, nullptr);
- // The MotionEvent should not be resampled because the second event came from a different device
- // than the previous event.
- assertMotionEventIsNotResampled(originalMotionEvent, motionFromSecondDevice);
-}
-
TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) {
MotionEvent motionEvent =
InputStream{{InputSample{10ms,
@@ -671,7 +648,15 @@ TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderInterpolation) {
mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
- assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+ assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+ {Pointer{.id = 0,
+ .x = 1.4f,
+ .y = 1.4f,
+ .isResampled = true},
+ Pointer{.id = 1,
+ .x = 2.4f,
+ .y = 2.4f,
+ .isResampled = true}});
}
TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderExtrapolation) {
@@ -693,7 +678,15 @@ TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderExtrapolation) {
mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
- assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+ assertMotionEventIsResampledAndCoordsNear(secondOriginalMotionEvent, secondMotionEvent,
+ {Pointer{.id = 1,
+ .x = 4.4f,
+ .y = 4.4f,
+ .isResampled = true},
+ Pointer{.id = 0,
+ .x = 3.4f,
+ .y = 3.4f,
+ .isResampled = true}});
}
TEST_F(ResamplerTest, MultiplePointerDifferentIdsInterpolation) {
diff --git a/libs/input/tests/TestEventMatchers.h b/libs/input/tests/TestEventMatchers.h
index dd2e40c025..8dbdcb310a 100644
--- a/libs/input/tests/TestEventMatchers.h
+++ b/libs/input/tests/TestEventMatchers.h
@@ -16,18 +16,36 @@
#pragma once
+#include <chrono>
+#include <cmath>
#include <ostream>
+#include <vector>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
#include <input/Input.h>
namespace android {
+using ::testing::Matcher;
+
/**
* This file contains a copy of Matchers from .../inputflinger/tests/TestEventMatchers.h. Ideally,
* implementations must not be duplicated.
* TODO(b/365606513): Find a way to share TestEventMatchers.h between inputflinger and libinput.
*/
+struct PointerArgs {
+ float x{0.0f};
+ float y{0.0f};
+ bool isResampled{false};
+};
+
+struct Sample {
+ std::chrono::nanoseconds eventTime{0};
+ std::vector<PointerArgs> pointers{};
+};
+
class WithDeviceIdMatcher {
public:
using is_gtest_matcher = void;
@@ -54,12 +72,18 @@ public:
using is_gtest_matcher = void;
explicit WithMotionActionMatcher(int32_t action) : mAction(action) {}
- bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
- bool matches = mAction == event.getAction();
- if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL) {
- matches &= (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+ bool MatchAndExplain(const MotionEvent& event, testing::MatchResultListener* listener) const {
+ if (mAction != event.getAction()) {
+ *listener << "expected " << MotionEvent::actionToString(mAction) << ", but got "
+ << MotionEvent::actionToString(event.getAction());
+ return false;
+ }
+ if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL &&
+ (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) == 0) {
+ *listener << "event with CANCEL action is missing FLAG_CANCELED";
+ return false;
}
- return matches;
+ return true;
}
void DescribeTo(std::ostream* os) const {
@@ -79,32 +103,92 @@ inline WithMotionActionMatcher WithMotionAction(int32_t action) {
return WithMotionActionMatcher(action);
}
-class MotionEventIsResampledMatcher {
+class WithSampleCountMatcher {
public:
using is_gtest_matcher = void;
+ explicit WithSampleCountMatcher(size_t sampleCount) : mExpectedSampleCount{sampleCount} {}
bool MatchAndExplain(const MotionEvent& motionEvent, std::ostream*) const {
- const size_t numSamples = motionEvent.getHistorySize() + 1;
- const size_t numPointers = motionEvent.getPointerCount();
- if (numPointers <= 0 || numSamples <= 0) {
+ return (motionEvent.getHistorySize() + 1) == mExpectedSampleCount;
+ }
+
+ void DescribeTo(std::ostream* os) const { *os << "sample count " << mExpectedSampleCount; }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "different sample count"; }
+
+private:
+ const size_t mExpectedSampleCount;
+};
+
+inline WithSampleCountMatcher WithSampleCount(size_t sampleCount) {
+ return WithSampleCountMatcher(sampleCount);
+}
+
+class WithSampleMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithSampleMatcher(size_t sampleIndex, const Sample& sample)
+ : mSampleIndex{sampleIndex}, mSample{sample} {}
+
+ bool MatchAndExplain(const MotionEvent& motionEvent, std::ostream* os) const {
+ if (motionEvent.getHistorySize() < mSampleIndex) {
+ *os << "sample index out of bounds";
+ return false;
+ }
+
+ if (motionEvent.getHistoricalEventTime(mSampleIndex) != mSample.eventTime.count()) {
+ *os << "event time mismatch. sample: "
+ << motionEvent.getHistoricalEventTime(mSampleIndex)
+ << " expected: " << mSample.eventTime.count();
+ return false;
+ }
+
+ if (motionEvent.getPointerCount() != mSample.pointers.size()) {
+ *os << "pointer count mismatch. sample: " << motionEvent.getPointerCount()
+ << " expected: " << mSample.pointers.size();
return false;
}
- for (size_t i = 0; i < numPointers; ++i) {
+
+ for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
+ ++pointerIndex) {
const PointerCoords& pointerCoords =
- motionEvent.getSamplePointerCoords()[numSamples * numPointers + i];
- if (!pointerCoords.isResampled) {
+ *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, mSampleIndex));
+
+ if ((std::abs(pointerCoords.getX() - mSample.pointers[pointerIndex].x) >
+ MotionEvent::ROUNDING_PRECISION) ||
+ (std::abs(pointerCoords.getY() - mSample.pointers[pointerIndex].y) >
+ MotionEvent::ROUNDING_PRECISION)) {
+ *os << "sample coordinates mismatch at pointer index " << pointerIndex
+ << ". sample: (" << pointerCoords.getX() << ", " << pointerCoords.getY()
+ << ") expected: (" << mSample.pointers[pointerIndex].x << ", "
+ << mSample.pointers[pointerIndex].y << ")";
+ return false;
+ }
+
+ if (motionEvent.isResampled(pointerIndex, mSampleIndex) !=
+ mSample.pointers[pointerIndex].isResampled) {
+ *os << "resampling flag mismatch. sample: "
+ << motionEvent.isResampled(pointerIndex, mSampleIndex)
+ << " expected: " << mSample.pointers[pointerIndex].isResampled;
return false;
}
}
return true;
}
- void DescribeTo(std::ostream* os) const { *os << "MotionEvent is resampled."; }
+ void DescribeTo(std::ostream* os) const { *os << "motion event sample properties match."; }
- void DescribeNegationTo(std::ostream* os) const { *os << "MotionEvent is not resampled."; }
+ void DescribeNegationTo(std::ostream* os) const {
+ *os << "motion event sample properties do not match expected properties.";
+ }
+
+private:
+ const size_t mSampleIndex;
+ const Sample mSample;
};
-inline MotionEventIsResampledMatcher MotionEventIsResampled() {
- return MotionEventIsResampledMatcher();
+inline WithSampleMatcher WithSample(size_t sampleIndex, const Sample& sample) {
+ return WithSampleMatcher(sampleIndex, sample);
}
+
} // namespace android
diff --git a/libs/input/tests/TfLiteMotionPredictor_test.cpp b/libs/input/tests/TfLiteMotionPredictor_test.cpp
index c3ac0b7cfb..0c19ebe651 100644
--- a/libs/input/tests/TfLiteMotionPredictor_test.cpp
+++ b/libs/input/tests/TfLiteMotionPredictor_test.cpp
@@ -89,23 +89,23 @@ TEST(TfLiteMotionPredictorTest, BuffersCopyTo) {
buffers.pushSample(/*timestamp=*/1,
{.position = {.x = 10, .y = 10},
.pressure = 0,
- .orientation = 0,
- .tilt = 0.2});
+ .tilt = 0.2,
+ .orientation = 0});
buffers.pushSample(/*timestamp=*/2,
{.position = {.x = 10, .y = 50},
.pressure = 0.4,
- .orientation = M_PI / 4,
- .tilt = 0.3});
+ .tilt = 0.3,
+ .orientation = M_PI / 4});
buffers.pushSample(/*timestamp=*/3,
{.position = {.x = 30, .y = 50},
.pressure = 0.5,
- .orientation = -M_PI / 4,
- .tilt = 0.4});
+ .tilt = 0.4,
+ .orientation = -M_PI / 4});
buffers.pushSample(/*timestamp=*/3,
{.position = {.x = 30, .y = 60},
.pressure = 0,
- .orientation = 0,
- .tilt = 0.5});
+ .tilt = 0.5,
+ .orientation = 0});
buffers.copyTo(*model);
const int zeroPadding = model->inputLength() - 3;
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 8d8b5300c1..9841c03826 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -571,11 +571,12 @@ TEST_F(TouchResamplingTest, TwoPointersAreResampledIndependently) {
std::chrono::nanoseconds frameTime;
std::vector<InputEventEntry> entries, expectedEntries;
- // full action for when a pointer with id=1 appears (some other pointer must already be present)
+ // full action for when a pointer with index=1 appears (some other pointer must already be
+ // present)
constexpr int32_t actionPointer1Down =
AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
- // full action for when a pointer with id=0 disappears (some other pointer must still remain)
+ // full action for when a pointer with index=0 disappears (some other pointer must still remain)
constexpr int32_t actionPointer0Up =
AMOTION_EVENT_ACTION_POINTER_UP + (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index bed31e27a8..24c2c74532 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -142,7 +142,7 @@ static inline AChoreographer* Choreographer_to_AChoreographer(Choreographer* cho
}
AChoreographer* AChoreographer_getInstance() {
- return Choreographer_to_AChoreographer(Choreographer::getForThread());
+ return Choreographer_to_AChoreographer(Choreographer::getForThread().get());
}
void AChoreographer_postFrameCallback(AChoreographer* choreographer,
diff --git a/libs/nativedisplay/include/surfacetexture/EGLConsumer.h b/libs/nativedisplay/include/surfacetexture/EGLConsumer.h
index 444722bf83..226a8a60af 100644
--- a/libs/nativedisplay/include/surfacetexture/EGLConsumer.h
+++ b/libs/nativedisplay/include/surfacetexture/EGLConsumer.h
@@ -113,18 +113,11 @@ public:
protected:
struct PendingRelease {
- PendingRelease()
- : isPending(false),
- currentTexture(-1),
- graphicBuffer(),
- display(nullptr),
- fence(nullptr) {}
+ PendingRelease() : isPending(false), currentTexture(-1), graphicBuffer() {}
bool isPending;
int currentTexture;
sp<GraphicBuffer> graphicBuffer;
- EGLDisplay display;
- EGLSyncKHR fence;
};
/**
@@ -250,13 +243,16 @@ protected:
* EGLConsumer maintains about a BufferQueue buffer slot.
*/
struct EglSlot {
- EglSlot() : mEglFence(EGL_NO_SYNC_KHR) {}
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ EglSlot() : mEglFence(EGL_NO_SYNC_KHR) {}
+#endif
/**
* mEglImage is the EGLImage created from mGraphicBuffer.
*/
sp<EglImage> mEglImage;
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
/**
* mFence is the EGL sync object that must signal before the buffer
* associated with this buffer slot may be dequeued. It is initialized
@@ -264,6 +260,7 @@ protected:
* on a compile-time option) set to a new sync object in updateTexImage.
*/
EGLSyncKHR mEglFence;
+#endif
};
/**
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index f1453bd64d..253aa18a24 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -343,8 +343,13 @@ protected:
* releaseBufferLocked overrides the ConsumerBase method to update the
* mEglSlots array in addition to the ConsumerBase.
*/
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer) override;
+#else
virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer,
- EGLDisplay display, EGLSyncKHR eglFence) override;
+ EGLDisplay display = EGL_NO_DISPLAY,
+ EGLSyncKHR eglFence = EGL_NO_SYNC_KHR) override;
+#endif
/**
* freeBufferLocked frees up the given buffer slot. If the slot has been
diff --git a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
index 275b7a4888..fad0f6cd0b 100644
--- a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
@@ -150,8 +150,7 @@ status_t EGLConsumer::releaseTexImage(SurfaceTexture& st) {
}
}
- err = st.releaseBufferLocked(buf, st.mSlots[buf].mGraphicBuffer, mEglDisplay,
- EGL_NO_SYNC_KHR);
+ err = st.releaseBufferLocked(buf, st.mSlots[buf].mGraphicBuffer);
if (err < NO_ERROR) {
EGC_LOGE("releaseTexImage: failed to release buffer: %s (%d)", strerror(-err), err);
return err;
@@ -222,7 +221,11 @@ void EGLConsumer::onAcquireBufferLocked(BufferItem* item, SurfaceTexture& st) {
}
void EGLConsumer::onReleaseBufferLocked(int buf) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ (void)buf;
+#else
mEglSlots[buf].mEglFence = EGL_NO_SYNC_KHR;
+#endif
}
status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRelease* pendingRelease,
@@ -234,14 +237,14 @@ status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRele
if (st.mOpMode != SurfaceTexture::OpMode::attachedToGL) {
EGC_LOGE("updateAndRelease: EGLConsumer is not attached to an OpenGL "
"ES context");
- st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+ st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
return INVALID_OPERATION;
}
// Confirm state.
err = checkAndUpdateEglStateLocked(st);
if (err != NO_ERROR) {
- st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+ st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
return err;
}
@@ -254,7 +257,7 @@ status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRele
if (err != NO_ERROR) {
EGC_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d", mEglDisplay,
slot);
- st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
+ st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
return UNKNOWN_ERROR;
}
@@ -266,8 +269,7 @@ status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRele
// release the old buffer, so instead we just drop the new frame.
// As we are still under lock since acquireBuffer, it is safe to
// release by slot.
- st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay,
- EGL_NO_SYNC_KHR);
+ st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
return err;
}
}
@@ -285,10 +287,15 @@ status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRele
// release old buffer
if (st.mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
if (pendingRelease == nullptr) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ status_t status = st.releaseBufferLocked(st.mCurrentTexture,
+ mCurrentTextureImage->graphicBuffer());
+#else
status_t status =
st.releaseBufferLocked(st.mCurrentTexture,
mCurrentTextureImage->graphicBuffer(), mEglDisplay,
mEglSlots[st.mCurrentTexture].mEglFence);
+#endif
if (status < NO_ERROR) {
EGC_LOGE("updateAndRelease: failed to release buffer: %s (%d)", strerror(-status),
status);
@@ -298,8 +305,6 @@ status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRele
} else {
pendingRelease->currentTexture = st.mCurrentTexture;
pendingRelease->graphicBuffer = mCurrentTextureImage->graphicBuffer();
- pendingRelease->display = mEglDisplay;
- pendingRelease->fence = mEglSlots[st.mCurrentTexture].mEglFence;
pendingRelease->isPending = true;
}
}
@@ -504,6 +509,11 @@ status_t EGLConsumer::syncForReleaseLocked(EGLDisplay dpy, SurfaceTexture& st) {
return err;
}
} else if (st.mUseFenceSync && SyncFeatures::getInstance().useFenceSync()) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ // Basically all clients are using native fence syncs. If they aren't, we lose nothing
+ // by waiting here, because the alternative can cause deadlocks (b/339705065).
+ glFinish();
+#else
EGLSyncKHR fence = mEglSlots[st.mCurrentTexture].mEglFence;
if (fence != EGL_NO_SYNC_KHR) {
// There is already a fence for the current slot. We need to
@@ -533,6 +543,7 @@ status_t EGLConsumer::syncForReleaseLocked(EGLDisplay dpy, SurfaceTexture& st) {
}
glFlush();
mEglSlots[st.mCurrentTexture].mEglFence = fence;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
}
}
diff --git a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
index 32b229d77c..1ffd382b80 100644
--- a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#define EGL_EGLEXT_PROTOTYPES
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
#include <gui/BufferQueue.h>
#include <surfacetexture/ImageConsumer.h>
#include <surfacetexture/SurfaceTexture.h>
@@ -64,8 +68,7 @@ sp<GraphicBuffer> ImageConsumer::dequeueBuffer(int* outSlotid, android_dataspace
// Wait on the producer fence for the buffer to be ready.
err = fenceWait(item.mFence->get(), fencePassThroughHandle);
if (err != OK) {
- st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
- EGL_NO_SYNC_KHR);
+ st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
return nullptr;
}
}
@@ -79,8 +82,7 @@ sp<GraphicBuffer> ImageConsumer::dequeueBuffer(int* outSlotid, android_dataspace
err = createFence(st.mUseFenceSync, &mImageSlots[slot].eglFence(), &display,
&releaseFenceId, fencePassThroughHandle);
if (OK != err) {
- st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
- EGL_NO_SYNC_KHR);
+ st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
return nullptr;
}
@@ -91,17 +93,40 @@ sp<GraphicBuffer> ImageConsumer::dequeueBuffer(int* outSlotid, android_dataspace
releaseFence);
if (err != OK) {
IMG_LOGE("dequeueImage: error adding release fence: %s (%d)", strerror(-err), err);
- st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY,
- EGL_NO_SYNC_KHR);
+ st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer);
return nullptr;
}
}
// Finally release the old buffer.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ EGLSyncKHR previousFence = mImageSlots[st.mCurrentTexture].eglFence();
+ if (previousFence != EGL_NO_SYNC_KHR) {
+ // Most platforms will be using native fences, so it's unlikely that we'll ever have to
+ // process an eglFence. Ideally we can remove this code eventually. In the mean time, do
+ // our best to wait for it so the buffer stays valid, otherwise return an error to the
+ // caller.
+ //
+ // EGL_SYNC_FLUSH_COMMANDS_BIT_KHR so that we don't wait forever on a fence that hasn't
+ // shown up on the GPU yet.
+ EGLint result = eglClientWaitSyncKHR(display, previousFence,
+ EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 1000000000);
+ if (result == EGL_FALSE) {
+ IMG_LOGE("dequeueBuffer: error %#x waiting for fence", eglGetError());
+ } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+ IMG_LOGE("dequeueBuffer: timeout waiting for fence");
+ }
+ eglDestroySyncKHR(display, previousFence);
+ }
+
+ status_t status = st.releaseBufferLocked(st.mCurrentTexture,
+ st.mSlots[st.mCurrentTexture].mGraphicBuffer);
+#else
status_t status =
st.releaseBufferLocked(st.mCurrentTexture,
st.mSlots[st.mCurrentTexture].mGraphicBuffer, display,
mImageSlots[st.mCurrentTexture].eglFence());
+#endif
if (status < NO_ERROR) {
IMG_LOGE("dequeueImage: failed to release buffer: %s (%d)", strerror(-status), status);
err = status;
diff --git a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
index ce232cc4c7..c0a1cc5c36 100644
--- a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
+++ b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
@@ -178,13 +178,21 @@ status_t SurfaceTexture::acquireBufferLocked(BufferItem* item, nsecs_t presentWh
return NO_ERROR;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+status_t SurfaceTexture::releaseBufferLocked(int buf, sp<GraphicBuffer> graphicBuffer) {
+#else
status_t SurfaceTexture::releaseBufferLocked(int buf, sp<GraphicBuffer> graphicBuffer,
EGLDisplay display, EGLSyncKHR eglFence) {
+#endif
// release the buffer if it hasn't already been discarded by the
// BufferQueue. This can happen, for example, when the producer of this
// buffer has reallocated the original buffer slot after this buffer
// was acquired.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_GL_FENCE_CLEANUP)
+ status_t err = ConsumerBase::releaseBufferLocked(buf, graphicBuffer);
+#else
status_t err = ConsumerBase::releaseBufferLocked(buf, graphicBuffer, display, eglFence);
+#endif
// We could be releasing an EGL/Vulkan buffer, even if not currently
// attached to a GL context.
mImageConsumer.onReleaseBufferLocked(buf);
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index f97eed5db3..f5a1db546d 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -222,6 +222,8 @@ int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpa
static_cast<int>(HAL_DATASPACE_BT2020_ITU_HLG));
static_assert(static_cast<int>(ADATASPACE_DEPTH) == static_cast<int>(HAL_DATASPACE_DEPTH));
static_assert(static_cast<int>(ADATASPACE_DYNAMIC_DEPTH) == static_cast<int>(HAL_DATASPACE_DYNAMIC_DEPTH));
+ static_assert(static_cast<int>(ADATASPACE_DISPLAY_BT2020) ==
+ static_cast<int>(HAL_DATASPACE_DISPLAY_BT2020));
if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
return -EINVAL;
diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h
index 8056d9ac4f..295a307c3c 100644
--- a/libs/nativewindow/include/android/data_space.h
+++ b/libs/nativewindow/include/android/data_space.h
@@ -578,6 +578,13 @@ enum ADataSpace : int32_t {
*/
ADATASPACE_BT2020_ITU_HLG = 302383104, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_HLG |
// ADATASPACE_RANGE_LIMITED
+ /**
+ * sRGB-encoded BT. 2020
+ *
+ * Uses full range, sRGB transfer and BT2020 standard.
+ */
+ ADATASPACE_DISPLAY_BT2020 = 142999552, // ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_SRGB
+ // | ADATASPACE_RANGE_FULL
/**
* Depth
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index 6f816bf614..10abb7c927 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -243,8 +243,7 @@ enum ANativeWindow_FrameRateCompatibility {
* There are no inherent restrictions on the frame rate of this window. When
* the system selects a frame rate other than what the app requested, the
* app will be able to run at the system frame rate without requiring pull
- * down. This value should be used when displaying game content, UIs, and
- * anything that isn't video.
+ * down. This value should be used when displaying game content.
*/
ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT = 0,
/**
@@ -256,7 +255,14 @@ enum ANativeWindow_FrameRateCompatibility {
* stuttering) than it would be if the system had chosen the app's requested
* frame rate. This value should be used for video content.
*/
- ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1
+ ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1,
+
+ /**
+ * The window requests a frame rate that is at least the specified frame rate.
+ * This value should be used for UIs, animations, scrolling, and anything that is not a game
+ * or video.
+ */
+ ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_AT_LEAST = 2
};
/**
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 33c303ae71..f669f7781e 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1060,12 +1060,7 @@ enum {
/**
* This surface will vote for the minimum refresh rate.
*/
- ANATIVEWINDOW_FRAME_RATE_MIN,
-
- /**
- * The surface requests a frame rate that is greater than or equal to `frameRate`.
- */
- ANATIVEWINDOW_FRAME_RATE_GTE
+ ANATIVEWINDOW_FRAME_RATE_MIN
};
/*
diff --git a/libs/nativewindow/tests/ANativeWindowTest.cpp b/libs/nativewindow/tests/ANativeWindowTest.cpp
index 937ff02241..51d0c8195a 100644
--- a/libs/nativewindow/tests/ANativeWindowTest.cpp
+++ b/libs/nativewindow/tests/ANativeWindowTest.cpp
@@ -50,14 +50,9 @@ protected:
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
ALOGV("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
- mItemConsumer = new BufferItemConsumer(GRALLOC_USAGE_SW_READ_OFTEN);
- mWindow = new TestableSurface(mItemConsumer->getSurface()->getIGraphicBufferProducer());
-#else
- BufferQueue::createBufferQueue(&mProducer, &mConsumer);
- mItemConsumer = new BufferItemConsumer(mConsumer, GRALLOC_USAGE_SW_READ_OFTEN);
- mWindow = new TestableSurface(mProducer);
-#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ sp<Surface> surface;
+ std::tie(mItemConsumer, surface) = BufferItemConsumer::create(GRALLOC_USAGE_SW_READ_OFTEN);
+ mWindow = new TestableSurface(surface->getIGraphicBufferProducer());
const int success = native_window_api_connect(mWindow.get(), NATIVE_WINDOW_API_CPU);
EXPECT_EQ(0, success);
}
diff --git a/libs/permission/Android.bp b/libs/permission/Android.bp
index 0eeca5469e..929f067bcd 100644
--- a/libs/permission/Android.bp
+++ b/libs/permission/Android.bp
@@ -16,6 +16,7 @@ aidl_interface {
double_loadable: true,
srcs: [
"aidl/android/content/AttributionSourceState.aidl",
+ "aidl/com/android/internal/app/IAppOpsCallback.aidl",
"aidl/android/permission/IPermissionChecker.aidl",
],
}
@@ -36,7 +37,6 @@ cc_library {
],
srcs: [
"AppOpsManager.cpp",
- "IAppOpsCallback.cpp",
"IAppOpsService.cpp",
"android/permission/PermissionChecker.cpp",
],
diff --git a/libs/permission/IAppOpsCallback.cpp b/libs/permission/IAppOpsCallback.cpp
deleted file mode 100644
index 2b3f462ab8..0000000000
--- a/libs/permission/IAppOpsCallback.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2013 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.
- */
-
-#define LOG_TAG "AppOpsCallback"
-
-#include <binder/IAppOpsCallback.h>
-
-#include <utils/Log.h>
-#include <binder/Parcel.h>
-#include <utils/String8.h>
-
-namespace android {
-
-// ----------------------------------------------------------------------
-
-class BpAppOpsCallback : public BpInterface<IAppOpsCallback>
-{
-public:
- explicit BpAppOpsCallback(const sp<IBinder>& impl)
- : BpInterface<IAppOpsCallback>(impl)
- {
- }
-
- virtual void opChanged(int32_t op, const String16& packageName) {
- Parcel data, reply;
- data.writeInterfaceToken(IAppOpsCallback::getInterfaceDescriptor());
- data.writeInt32(op);
- data.writeString16(packageName);
- remote()->transact(OP_CHANGED_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
- }
-};
-
-IMPLEMENT_META_INTERFACE(AppOpsCallback, "com.android.internal.app.IAppOpsCallback")
-
-// ----------------------------------------------------------------------
-
-// NOLINTNEXTLINE(google-default-arguments)
-status_t BnAppOpsCallback::onTransact(
- uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-{
- switch(code) {
- case OP_CHANGED_TRANSACTION: {
- CHECK_INTERFACE(IAppOpsCallback, data, reply);
- int32_t op = data.readInt32();
- String16 packageName;
- (void)data.readString16(&packageName);
- opChanged(op, packageName);
- return NO_ERROR;
- } break;
- default:
- return BBinder::onTransact(code, data, reply, flags);
- }
-}
-
-} // namespace android
diff --git a/libs/permission/aidl/com/android/internal/app/IAppOpsCallback.aidl b/libs/permission/aidl/com/android/internal/app/IAppOpsCallback.aidl
new file mode 100644
index 0000000000..36b19dfb25
--- /dev/null
+++ b/libs/permission/aidl/com/android/internal/app/IAppOpsCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 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 com.android.internal.app;
+
+oneway interface IAppOpsCallback {
+ void opChanged(int op, int uid, String packageName, String persistentDeviceId);
+}
diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h
index 243532bc4d..a22c975588 100644
--- a/libs/permission/include/binder/AppOpsManager.h
+++ b/libs/permission/include/binder/AppOpsManager.h
@@ -148,7 +148,10 @@ public:
OP_BLUETOOTH_ADVERTISE = 114,
OP_RECORD_INCOMING_PHONE_AUDIO = 115,
OP_NEARBY_WIFI_DEVICES = 116,
- _NUM_OP = 117
+ // 116 - 154 omitted due to lack of use in native
+ OP_CONTROL_AUDIO = 154,
+ OP_CONTROL_AUDIO_PARTIAL = 155,
+ _NUM_OP = 156,
};
enum {
@@ -177,10 +180,10 @@ public:
void finishOp(int32_t op, int32_t uid, const String16& callingPackage,
const std::optional<String16>& attributionTag);
void startWatchingMode(int32_t op, const String16& packageName,
- const sp<IAppOpsCallback>& callback);
+ const sp<com::android::internal::app::IAppOpsCallback>& callback);
void startWatchingMode(int32_t op, const String16& packageName, int32_t flags,
- const sp<IAppOpsCallback>& callback);
- void stopWatchingMode(const sp<IAppOpsCallback>& callback);
+ const sp<com::android::internal::app::IAppOpsCallback>& callback);
+ void stopWatchingMode(const sp<com::android::internal::app::IAppOpsCallback>& callback);
int32_t permissionToOpCode(const String16& permission);
void setCameraAudioRestriction(int32_t mode);
diff --git a/libs/permission/include/binder/IAppOpsCallback.h b/libs/permission/include/binder/IAppOpsCallback.h
deleted file mode 100644
index eb76f57bf8..0000000000
--- a/libs/permission/include/binder/IAppOpsCallback.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2013 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
-
-#ifndef __ANDROID_VNDK__
-
-#include <binder/IInterface.h>
-
-namespace android {
-
-// ----------------------------------------------------------------------
-
-class IAppOpsCallback : public IInterface
-{
-public:
- DECLARE_META_INTERFACE(AppOpsCallback)
-
- virtual void opChanged(int32_t op, const String16& packageName) = 0;
-
- enum {
- OP_CHANGED_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION
- };
-};
-
-// ----------------------------------------------------------------------
-
-class BnAppOpsCallback : public BnInterface<IAppOpsCallback>
-{
-public:
- // NOLINTNEXTLINE(google-default-arguments)
- virtual status_t onTransact( uint32_t code,
- const Parcel& data,
- Parcel* reply,
- uint32_t flags = 0);
-};
-
-// ----------------------------------------------------------------------
-
-} // namespace android
-
-#else // __ANDROID_VNDK__
-#error "This header is not visible to vendors"
-#endif // __ANDROID_VNDK__
diff --git a/libs/permission/include/binder/IAppOpsService.h b/libs/permission/include/binder/IAppOpsService.h
index 918fcdbce1..1468fd9415 100644
--- a/libs/permission/include/binder/IAppOpsService.h
+++ b/libs/permission/include/binder/IAppOpsService.h
@@ -16,7 +16,8 @@
#pragma once
-#include <binder/IAppOpsCallback.h>
+#include <com/android/internal/app/IAppOpsCallback.h>
+#include <com/android/internal/app/BnAppOpsCallback.h>
#include <binder/IInterface.h>
#include <optional>
@@ -27,6 +28,8 @@
namespace android {
+using IAppOpsCallback = ::com::android::internal::app::IAppOpsCallback;
+
// ----------------------------------------------------------------------
class IAppOpsService : public IInterface
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index d248ea0b84..7f207f0670 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -105,6 +105,7 @@ filegroup {
"skia/filters/KawaseBlurDualFilter.cpp",
"skia/filters/KawaseBlurFilter.cpp",
"skia/filters/LinearEffect.cpp",
+ "skia/filters/LutShader.cpp",
"skia/filters/MouriMap.cpp",
"skia/filters/StretchShaderFactory.cpp",
"skia/filters/EdgeExtensionShaderFactory.cpp",
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 907590a236..873fc67c65 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -107,16 +107,15 @@ ftl::Future<FenceResult> RenderEngine::drawLayers(const DisplaySettings& display
return resultFuture;
}
-ftl::Future<FenceResult> RenderEngine::drawGainmap(
- const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+ftl::Future<FenceResult> RenderEngine::tonemapAndDrawGainmap(
const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
- float hdrSdrRatio, ui::Dataspace dataspace,
+ float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
const std::shared_ptr<ExternalTexture>& gainmap) {
const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
std::future<FenceResult> resultFuture = resultPromise->get_future();
updateProtectedContext({}, {sdr.get(), hdr.get(), gainmap.get()});
- drawGainmapInternal(std::move(resultPromise), sdr, std::move(sdrFence), hdr,
- std::move(hdrFence), hdrSdrRatio, dataspace, gainmap);
+ tonemapAndDrawGainmapInternal(std::move(resultPromise), hdr, std::move(hdrFence), hdrSdrRatio,
+ dataspace, sdr, gainmap);
return resultFuture;
}
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
index a9264b3914..595573d24a 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -17,6 +17,7 @@
#include <RenderEngineBench.h>
#include <android-base/file.h>
#include <benchmark/benchmark.h>
+#include <com_android_graphics_libgui_flags.h>
#include <gui/SurfaceComposerClient.h>
#include <log/log.h>
#include <renderengine/ExternalTexture.h>
@@ -321,5 +322,7 @@ BENCHMARK_CAPTURE(BM_homescreen_blur, kawase_dual_filter, RenderEngine::Threaded
BENCHMARK_CAPTURE(BM_homescreen, SkiaGLThreaded, RenderEngine::Threaded::YES,
RenderEngine::GraphicsApi::GL);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_EDGE_EXTENSION_SHADER
BENCHMARK_CAPTURE(BM_homescreen_edgeExtension, SkiaGLThreaded, RenderEngine::Threaded::YES,
RenderEngine::GraphicsApi::GL);
+#endif
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index 859ae8b6e2..ecb16b2e17 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -16,6 +16,7 @@
#pragma once
+#include <gui/DisplayLuts.h>
#include <math/mat4.h>
#include <math/vec3.h>
#include <renderengine/ExternalTexture.h>
@@ -145,6 +146,8 @@ struct LayerSettings {
// If white point nits are unknown, then this layer is assumed to have the
// same luminance as the brightest layer in the scene.
float whitePointNits = -1.f;
+
+ std::shared_ptr<gui::DisplayLuts> luts;
};
// Keep in sync with custom comparison function in
@@ -187,7 +190,7 @@ static inline bool operator==(const LayerSettings& lhs, const LayerSettings& rhs
lhs.blurRegionTransform == rhs.blurRegionTransform &&
lhs.stretchEffect == rhs.stretchEffect &&
lhs.edgeExtensionEffect == rhs.edgeExtensionEffect &&
- lhs.whitePointNits == rhs.whitePointNits;
+ lhs.whitePointNits == rhs.whitePointNits && lhs.luts == rhs.luts;
}
static inline void PrintTo(const Buffer& settings, ::std::ostream* os) {
@@ -298,6 +301,10 @@ static inline void PrintTo(const LayerSettings& settings, ::std::ostream* os) {
*os << "\n .edgeExtensionEffect = " << settings.edgeExtensionEffect;
}
*os << "\n .whitePointNits = " << settings.whitePointNits;
+ if (settings.luts) {
+ *os << "\n .luts = ";
+ PrintTo(settings.luts, os);
+ }
*os << "\n}";
}
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 0fd982e812..c2dd4ae97e 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -38,6 +38,14 @@
#define PROPERTY_DEBUG_RENDERENGINE_BACKEND "debug.renderengine.backend"
/**
+ * Allows opting particular devices into an initial preview rollout of RenderEngine on Graphite.
+ *
+ * Only applicable within SurfaceFlinger, and if relevant aconfig flags are enabled.
+ */
+#define PROPERTY_DEBUG_RENDERENGINE_GRAPHITE_PREVIEW_OPTIN \
+ "debug.renderengine.graphite_preview_optin"
+
+/**
* Turns on recording of skia commands in SkiaGL version of the RE. This property
* defines number of milliseconds for the recording to take place. A non zero value
* turns on the recording.
@@ -209,12 +217,17 @@ public:
const std::shared_ptr<ExternalTexture>& buffer,
base::unique_fd&& bufferFence);
- virtual ftl::Future<FenceResult> drawGainmap(const std::shared_ptr<ExternalTexture>& sdr,
- base::borrowed_fd&& sdrFence,
- const std::shared_ptr<ExternalTexture>& hdr,
- base::borrowed_fd&& hdrFence, float hdrSdrRatio,
- ui::Dataspace dataspace,
- const std::shared_ptr<ExternalTexture>& gainmap);
+ // Tonemaps an HDR input image and draws an SDR rendition, plus a gainmap
+ // describing how to recover the HDR image.
+ //
+ // The HDR input image is ALWAYS encoded with an sRGB transfer function and
+ // is a floating point format. Accordingly, the hdrSdrRatio describes the
+ // max luminance in the HDR input image above SDR, and the dataspace
+ // describes the input primaries.
+ virtual ftl::Future<FenceResult> tonemapAndDrawGainmap(
+ const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+ float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
+ const std::shared_ptr<ExternalTexture>& gainmap);
// Clean-up method that should be called on the main thread after the
// drawFence returned by drawLayers fires. This method will free up
@@ -302,11 +315,10 @@ protected:
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) = 0;
- virtual void drawGainmapInternal(
+ virtual void tonemapAndDrawGainmapInternal(
const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
- const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
- float hdrSdrRatio, ui::Dataspace dataspace,
+ float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
const std::shared_ptr<ExternalTexture>& gainmap) = 0;
};
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index fb8331d870..c42e4034e0 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -46,17 +46,16 @@ public:
ftl::Future<FenceResult>(const DisplaySettings&, const std::vector<LayerSettings>&,
const std::shared_ptr<ExternalTexture>&,
base::unique_fd&&));
- MOCK_METHOD7(drawGainmap,
+ MOCK_METHOD6(tonemapAndDrawGainmap,
ftl::Future<FenceResult>(const std::shared_ptr<ExternalTexture>&,
- base::borrowed_fd&&,
- const std::shared_ptr<ExternalTexture>&,
base::borrowed_fd&&, float, ui::Dataspace,
+ const std::shared_ptr<ExternalTexture>&,
const std::shared_ptr<ExternalTexture>&));
- MOCK_METHOD8(drawGainmapInternal,
+ MOCK_METHOD7(tonemapAndDrawGainmapInternal,
void(const std::shared_ptr<std::promise<FenceResult>>&&,
- const std::shared_ptr<ExternalTexture>&, base::borrowed_fd&&,
const std::shared_ptr<ExternalTexture>&, base::borrowed_fd&&, float,
- ui::Dataspace, const std::shared_ptr<ExternalTexture>&));
+ ui::Dataspace, const std::shared_ptr<ExternalTexture>&,
+ const std::shared_ptr<ExternalTexture>&));
MOCK_METHOD5(drawLayersInternal,
void(const std::shared_ptr<std::promise<FenceResult>>&&, const DisplaySettings&,
const std::vector<LayerSettings>&, const std::shared_ptr<ExternalTexture>&,
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index 3b0f03671b..9f64d2c4db 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -14,6 +14,9 @@
* limitations under the License.
*/
#include "Cache.h"
+
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
#include "AutoBackendTexture.h"
#include "SkiaRenderEngine.h"
#include "android-base/unique_fd.h"
@@ -28,6 +31,7 @@
#include "utils/Timers.h"
#include <com_android_graphics_libgui_flags.h>
+#include <common/trace.h>
namespace android::renderengine::skia {
@@ -659,6 +663,7 @@ static void drawEdgeExtensionLayers(SkiaRenderEngine* renderengine, const Displa
// in external/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
// gPrintSKSL = true
void Cache::primeShaderCache(SkiaRenderEngine* renderengine, PrimeCacheConfig config) {
+ SFTRACE_CALL();
const int previousCount = renderengine->reportShadersCompiled();
if (previousCount) {
ALOGD("%d Shaders already compiled before Cache::primeShaderCache ran\n", previousCount);
@@ -724,24 +729,29 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine, PrimeCacheConfig co
impl::ExternalTexture::Usage::WRITEABLE);
if (config.cacheHolePunchLayer) {
+ SFTRACE_NAME("cacheHolePunchLayer");
drawHolePunchLayer(renderengine, display, dstTexture);
}
if (config.cacheSolidLayers) {
+ SFTRACE_NAME("cacheSolidLayers");
drawSolidLayers(renderengine, display, dstTexture);
drawSolidLayers(renderengine, p3Display, dstTexture);
}
if (config.cacheSolidDimmedLayers) {
+ SFTRACE_NAME("cacheSolidDimmedLayers");
drawSolidDimmedLayers(renderengine, display, dstTexture);
}
if (config.cacheShadowLayers) {
+ SFTRACE_NAME("cacheShadowLayers");
drawShadowLayers(renderengine, display, srcTexture);
drawShadowLayers(renderengine, p3Display, srcTexture);
}
if (renderengine->supportsBackgroundBlur()) {
+ SFTRACE_NAME("supportsBackgroundBlur");
drawBlurLayers(renderengine, display, dstTexture);
}
@@ -776,32 +786,38 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine, PrimeCacheConfig co
for (auto texture : textures) {
if (config.cacheImageLayers) {
+ SFTRACE_NAME("cacheImageLayers");
drawImageLayers(renderengine, display, dstTexture, texture);
}
if (config.cacheImageDimmedLayers) {
+ SFTRACE_NAME("cacheImageDimmedLayers");
drawImageDimmedLayers(renderengine, display, dstTexture, texture);
drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture);
drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture);
}
if (config.cacheClippedLayers) {
+ SFTRACE_NAME("cacheClippedLayers");
// Draw layers for b/185569240.
drawClippedLayers(renderengine, display, dstTexture, texture);
}
if (com::android::graphics::libgui::flags::edge_extension_shader() &&
config.cacheEdgeExtension) {
+ SFTRACE_NAME("cacheEdgeExtension");
drawEdgeExtensionLayers(renderengine, display, dstTexture, texture);
drawEdgeExtensionLayers(renderengine, p3Display, dstTexture, texture);
}
}
if (config.cachePIPImageLayers) {
+ SFTRACE_NAME("cachePIPImageLayers");
drawPIPImageLayer(renderengine, display, dstTexture, externalTexture);
}
if (config.cacheTransparentImageDimmedLayers) {
+ SFTRACE_NAME("cacheTransparentImageDimmedLayers");
drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture,
externalTexture);
drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture);
@@ -811,10 +827,12 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine, PrimeCacheConfig co
}
if (config.cacheClippedDimmedImageLayers) {
+ SFTRACE_NAME("cacheClippedDimmedImageLayers");
drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
}
if (config.cacheUltraHDR) {
+ SFTRACE_NAME("cacheUltraHDR");
drawBT2020ClippedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
drawBT2020ImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
@@ -833,7 +851,10 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine, PrimeCacheConfig co
};
auto layers = std::vector<LayerSettings>{layer};
// call get() to make it synchronous
- renderengine->drawLayers(display, layers, dstTexture, base::unique_fd()).get();
+ {
+ SFTRACE_NAME("finalLayer");
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd()).get();
+ }
const nsecs_t timeAfter = systemTime();
const float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.cpp b/libs/renderengine/skia/GaneshVkRenderEngine.cpp
index a3a43e20be..cc73f405a6 100644
--- a/libs/renderengine/skia/GaneshVkRenderEngine.cpp
+++ b/libs/renderengine/skia/GaneshVkRenderEngine.cpp
@@ -21,12 +21,15 @@
#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
+#include <android-base/stringprintf.h>
#include <common/trace.h>
#include <log/log_main.h>
#include <sync/sync.h>
namespace android::renderengine::skia {
+using base::StringAppendF;
+
std::unique_ptr<GaneshVkRenderEngine> GaneshVkRenderEngine::create(
const RenderEngineCreationArgs& args) {
std::unique_ptr<GaneshVkRenderEngine> engine(new GaneshVkRenderEngine(args));
@@ -111,4 +114,9 @@ base::unique_fd GaneshVkRenderEngine::flushAndSubmit(SkiaGpuContext* context,
return res;
}
+void GaneshVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
+ StringAppendF(&result, "\n ------------RE Vulkan (Ganesh)----------\n");
+ SkiaVkRenderEngine::appendBackendSpecificInfoToDump(result);
+}
+
} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.h b/libs/renderengine/skia/GaneshVkRenderEngine.h
index e6123c21bf..ba17f71201 100644
--- a/libs/renderengine/skia/GaneshVkRenderEngine.h
+++ b/libs/renderengine/skia/GaneshVkRenderEngine.h
@@ -28,6 +28,7 @@ protected:
std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) override;
void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override;
+ void appendBackendSpecificInfoToDump(std::string& result) override;
private:
GaneshVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {}
diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.cpp b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp
index 390ad6efd1..a9332fa4e1 100644
--- a/libs/renderengine/skia/GraphiteVkRenderEngine.cpp
+++ b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp
@@ -25,6 +25,7 @@
#include <include/gpu/graphite/Recording.h>
#include <include/gpu/graphite/vk/VulkanGraphiteTypes.h>
+#include <android-base/stringprintf.h>
#include <log/log_main.h>
#include <sync/sync.h>
@@ -33,6 +34,8 @@
namespace android::renderengine::skia {
+using base::StringAppendF;
+
std::unique_ptr<GraphiteVkRenderEngine> GraphiteVkRenderEngine::create(
const RenderEngineCreationArgs& args) {
std::unique_ptr<GraphiteVkRenderEngine> engine(new GraphiteVkRenderEngine(args));
@@ -139,4 +142,9 @@ base::unique_fd GraphiteVkRenderEngine::flushAndSubmit(SkiaGpuContext* context,
return drawFenceFd;
}
+void GraphiteVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
+ StringAppendF(&result, "\n ------------RE Vulkan (Graphite)----------\n");
+ SkiaVkRenderEngine::appendBackendSpecificInfoToDump(result);
+}
+
} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.h b/libs/renderengine/skia/GraphiteVkRenderEngine.h
index cf24a3b756..33a47f1a7f 100644
--- a/libs/renderengine/skia/GraphiteVkRenderEngine.h
+++ b/libs/renderengine/skia/GraphiteVkRenderEngine.h
@@ -30,6 +30,7 @@ protected:
std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) override;
void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override;
+ void appendBackendSpecificInfoToDump(std::string& result) override;
private:
GraphiteVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {}
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 4ef7d5bccb..ddae9fc78f 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -541,7 +541,7 @@ int SkiaGLRenderEngine::getContextPriority() {
void SkiaGLRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
const GLExtensions& extensions = GLExtensions::getInstance();
- StringAppendF(&result, "\n ------------RE GLES------------\n");
+ StringAppendF(&result, "\n ------------RE GLES (Ganesh)------------\n");
StringAppendF(&result, "EGL implementation : %s\n", extensions.getEGLVersion());
StringAppendF(&result, "%s\n", extensions.getEGLExtensions());
StringAppendF(&result, "GLES: %s, %s, %s\n", extensions.getVendor(), extensions.getRenderer(),
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index e62639f3c3..5f2d1b1be6 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -543,6 +543,21 @@ sk_sp<SkShader> SkiaRenderEngine::createRuntimeEffectShader(
}
}
+ if (graphicBuffer && parameters.layer.luts) {
+ const bool dimInLinearSpace = parameters.display.dimmingStage !=
+ aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF;
+ const ui::Dataspace runtimeEffectDataspace = !dimInLinearSpace
+ ? static_cast<ui::Dataspace>(
+ (parameters.outputDataSpace & ui::Dataspace::STANDARD_MASK) |
+ ui::Dataspace::TRANSFER_GAMMA2_2 |
+ (parameters.outputDataSpace & ui::Dataspace::RANGE_MASK))
+ : parameters.outputDataSpace;
+
+ shader = mLutShader.lutShader(shader, parameters.layer.luts,
+ parameters.layer.sourceDataspace,
+ toSkColorSpace(runtimeEffectDataspace));
+ }
+
if (parameters.requiresLinearEffect) {
const auto format = targetBuffer != nullptr
? std::optional<ui::PixelFormat>(
@@ -561,14 +576,14 @@ sk_sp<SkShader> SkiaRenderEngine::createRuntimeEffectShader(
if (usingLocalTonemap) {
const float inputRatio =
hdrType == HdrRenderType::GENERIC_HDR ? 1.0f : parameters.layerDimmingRatio;
- static MouriMap kMapper;
- shader = kMapper.mouriMap(getActiveContext(), shader, inputRatio,
- parameters.display.targetHdrSdrRatio);
+ shader = localTonemap(shader, inputRatio, parameters.display.targetHdrSdrRatio);
}
// disable tonemapping if we already locally tonemapped
- auto inputDataspace =
- usingLocalTonemap ? parameters.outputDataSpace : parameters.layer.sourceDataspace;
+ // skip tonemapping if the luts is in use
+ auto inputDataspace = usingLocalTonemap || (graphicBuffer && parameters.layer.luts)
+ ? parameters.outputDataSpace
+ : parameters.layer.sourceDataspace;
auto effect =
shaders::LinearEffect{.inputDataspace = inputDataspace,
.outputDataspace = parameters.outputDataSpace,
@@ -602,6 +617,12 @@ sk_sp<SkShader> SkiaRenderEngine::createRuntimeEffectShader(
return shader;
}
+sk_sp<SkShader> SkiaRenderEngine::localTonemap(sk_sp<SkShader> shader, float inputMultiplier,
+ float targetHdrSdrRatio) {
+ static MouriMap kMapper;
+ return kMapper.mouriMap(getActiveContext(), shader, inputMultiplier, targetHdrSdrRatio);
+}
+
void SkiaRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) {
if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
// Record display settings when capture is running.
@@ -817,8 +838,7 @@ void SkiaRenderEngine::drawLayersInternal(
LOG_ALWAYS_FATAL_IF(activeSurface == dstSurface);
LOG_ALWAYS_FATAL_IF(canvas == dstCanvas);
- // save a snapshot of the activeSurface to use as input to the blur shaders
- blurInput = activeSurface->makeImageSnapshot();
+ blurInput = activeSurface->makeTemporaryImage();
// blit the offscreen framebuffer into the destination AHB. This ensures that
// even if the blurred image does not cover the screen (for example, during
@@ -832,12 +852,9 @@ void SkiaRenderEngine::drawLayersInternal(
dstCanvas->drawAnnotation(SkRect::Make(dstCanvas->imageInfo().dimensions()),
String8::format("SurfaceID|%" PRId64, id).c_str(),
nullptr);
- dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
- } else {
- activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint);
}
+ dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
}
-
// assign dstCanvas to canvas and ensure that the canvas state is up to date
canvas = dstCanvas;
surfaceAutoSaveRestore.replace(canvas);
@@ -870,12 +887,6 @@ void SkiaRenderEngine::drawLayersInternal(
if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) {
std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;
- // if multiple layers have blur, then we need to take a snapshot now because
- // only the lowest layer will have blurImage populated earlier
- if (!blurInput) {
- blurInput = activeSurface->makeImageSnapshot();
- }
-
// rect to be blurred in the coordinate space of blurInput
SkRect blurRect = canvas->getTotalMatrix().mapRect(bounds.rect());
@@ -899,6 +910,29 @@ void SkiaRenderEngine::drawLayersInternal(
// TODO(b/182216890): Filter out empty layers earlier
if (blurRect.width() > 0 && blurRect.height() > 0) {
+ // if multiple layers have blur, then we need to take a snapshot now because
+ // only the lowest layer will have blurImage populated earlier
+ if (!blurInput) {
+ bool requiresCrossFadeWithBlurInput = false;
+ if (layer.backgroundBlurRadius > 0 &&
+ layer.backgroundBlurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
+ requiresCrossFadeWithBlurInput = true;
+ }
+ for (auto region : layer.blurRegions) {
+ if (region.blurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
+ requiresCrossFadeWithBlurInput = true;
+ }
+ }
+ if (requiresCrossFadeWithBlurInput) {
+ // If we require cross fading with the blur input, we need to make sure we
+ // make a copy of the surface to the image since we will be writing to the
+ // surface while sampling the blurInput.
+ blurInput = activeSurface->makeImageSnapshot();
+ } else {
+ blurInput = activeSurface->makeTemporaryImage();
+ }
+ }
+
if (layer.backgroundBlurRadius > 0) {
SFTRACE_NAME("BackgroundBlur");
auto blurredImage = mBlurFilter->generate(context, layer.backgroundBlurRadius,
@@ -1204,44 +1238,58 @@ void SkiaRenderEngine::drawLayersInternal(
resultPromise->set_value(std::move(drawFence));
}
-void SkiaRenderEngine::drawGainmapInternal(
+void SkiaRenderEngine::tonemapAndDrawGainmapInternal(
const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
- const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
- float hdrSdrRatio, ui::Dataspace dataspace,
+ float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
const std::shared_ptr<ExternalTexture>& gainmap) {
std::lock_guard<std::mutex> lock(mRenderingMutex);
auto context = getActiveContext();
- auto surfaceTextureRef = getOrCreateBackendTexture(gainmap->getBuffer(), true);
- sk_sp<SkSurface> dstSurface =
- surfaceTextureRef->getOrCreateSurface(ui::Dataspace::V0_SRGB_LINEAR);
-
- waitFence(context, sdrFence);
- const auto sdrTextureRef = getOrCreateBackendTexture(sdr->getBuffer(), false);
- const auto sdrImage = sdrTextureRef->makeImage(dataspace, kPremul_SkAlphaType);
- const auto sdrShader =
- sdrImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
- SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
- nullptr);
+ auto gainmapTextureRef = getOrCreateBackendTexture(gainmap->getBuffer(), true);
+ sk_sp<SkSurface> gainmapSurface =
+ gainmapTextureRef->getOrCreateSurface(ui::Dataspace::V0_SRGB_LINEAR);
+
+ auto sdrTextureRef = getOrCreateBackendTexture(sdr->getBuffer(), true);
+ sk_sp<SkSurface> sdrSurface = sdrTextureRef->getOrCreateSurface(dataspace);
+
waitFence(context, hdrFence);
const auto hdrTextureRef = getOrCreateBackendTexture(hdr->getBuffer(), false);
const auto hdrImage = hdrTextureRef->makeImage(dataspace, kPremul_SkAlphaType);
const auto hdrShader =
hdrImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
- SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
+ SkSamplingOptions({SkFilterMode::kNearest, SkMipmapMode::kNone}),
nullptr);
+ const auto tonemappedShader = localTonemap(hdrShader, 1.0f, 1.0f);
+
static GainmapFactory kGainmapFactory;
- const auto gainmapShader = kGainmapFactory.createSkShader(sdrShader, hdrShader, hdrSdrRatio);
+ const auto gainmapShader =
+ kGainmapFactory.createSkShader(tonemappedShader, hdrShader, hdrSdrRatio);
- const auto canvas = dstSurface->getCanvas();
- SkPaint paint;
- paint.setShader(gainmapShader);
- paint.setBlendMode(SkBlendMode::kSrc);
- canvas->drawPaint(paint);
+ sp<Fence> drawFence;
- auto drawFence = sp<Fence>::make(flushAndSubmit(context, dstSurface));
- trace(drawFence);
+ {
+ const auto canvas = sdrSurface->getCanvas();
+ SkPaint paint;
+ paint.setShader(tonemappedShader);
+ paint.setBlendMode(SkBlendMode::kSrc);
+ canvas->drawPaint(paint);
+
+ drawFence = sp<Fence>::make(flushAndSubmit(context, sdrSurface));
+ trace(drawFence);
+ }
+
+ {
+ const auto canvas = gainmapSurface->getCanvas();
+ SkPaint paint;
+ paint.setShader(gainmapShader);
+ paint.setBlendMode(SkBlendMode::kSrc);
+ canvas->drawPaint(paint);
+
+ auto gmFence = sp<Fence>::make(flushAndSubmit(context, gainmapSurface));
+ trace(gmFence);
+ drawFence = Fence::merge("gm-ss", drawFence, gmFence);
+ }
resultPromise->set_value(std::move(drawFence));
}
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index b5f8898263..92b7af985c 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -39,6 +39,7 @@
#include "filters/BlurFilter.h"
#include "filters/EdgeExtensionShaderFactory.h"
#include "filters/LinearEffect.h"
+#include "filters/LutShader.h"
#include "filters/StretchShaderFactory.h"
class SkData;
@@ -142,13 +143,11 @@ private:
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
base::unique_fd&& bufferFence) override final;
- void drawGainmapInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
- const std::shared_ptr<ExternalTexture>& sdr,
- base::borrowed_fd&& sdrFence,
- const std::shared_ptr<ExternalTexture>& hdr,
- base::borrowed_fd&& hdrFence, float hdrSdrRatio,
- ui::Dataspace dataspace,
- const std::shared_ptr<ExternalTexture>& gainmap) override final;
+ void tonemapAndDrawGainmapInternal(
+ const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+ const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+ float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
+ const std::shared_ptr<ExternalTexture>& gainmap) override final;
void dump(std::string& result) override final;
@@ -167,6 +166,8 @@ private:
};
sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
+ sk_sp<SkShader> localTonemap(sk_sp<SkShader>, float inputMultiplier, float targetHdrSdrRatio);
+
const PixelFormat mDefaultPixelFormat;
// Identifier used for various mappings of layers to various
@@ -184,6 +185,7 @@ private:
StretchShaderFactory mStretchShaderFactory;
EdgeExtensionShaderFactory mEdgeExtensionShaderFactory;
+ LutShader mLutShader;
sp<Fence> mLastDrawFence;
BlurFilter* mBlurFilter = nullptr;
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index 677a2b63b2..177abe6c9f 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -169,24 +169,26 @@ int SkiaVkRenderEngine::getContextPriority() {
}
void SkiaVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
- StringAppendF(&result, "\n ------------RE Vulkan----------\n");
- StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.isInitialized());
- StringAppendF(&result, "\n Vulkan protected device initialized: %d\n",
+ // Subclasses will prepend a backend-specific name / section header
+ StringAppendF(&result, "Vulkan device initialized: %d\n", sVulkanInterface.isInitialized());
+ StringAppendF(&result, "Vulkan protected device initialized: %d\n",
sProtectedContentVulkanInterface.isInitialized());
if (!sVulkanInterface.isInitialized()) {
return;
}
- StringAppendF(&result, "\n Instance extensions:\n");
+ StringAppendF(&result, "Instance extensions: [\n");
for (const auto& name : sVulkanInterface.getInstanceExtensionNames()) {
- StringAppendF(&result, "\n %s\n", name.c_str());
+ StringAppendF(&result, " %s\n", name.c_str());
}
+ StringAppendF(&result, "]\n");
- StringAppendF(&result, "\n Device extensions:\n");
+ StringAppendF(&result, "Device extensions: [\n");
for (const auto& name : sVulkanInterface.getDeviceExtensionNames()) {
- StringAppendF(&result, "\n %s\n", name.c_str());
+ StringAppendF(&result, " %s\n", name.c_str());
}
+ StringAppendF(&result, "]\n");
}
} // namespace skia
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
index d2bb3d53cf..88b04df58d 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -81,7 +81,7 @@ protected:
SkiaRenderEngine::Contexts createContexts() override;
bool supportsProtectedContentImpl() const override;
bool useProtectedContextImpl(GrProtected isProtected) override;
- void appendBackendSpecificInfoToDump(std::string& result) override;
+ virtual void appendBackendSpecificInfoToDump(std::string& result) override;
// TODO: b/300533018 - refactor this to be non-static
static VulkanInterface& getVulkanInterface(bool protectedContext);
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
index 37b69f6590..7331bbc418 100644
--- a/libs/renderengine/skia/VulkanInterface.cpp
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -204,10 +204,10 @@ static skgpu::VulkanGetProc sGetProc = [](const char* proc_name,
BAIL("[%s] null", #expr); \
}
-#define VK_CHECK(expr) \
- if ((expr) != VK_SUCCESS) { \
- BAIL("[%s] failed. err = %d", #expr, expr); \
- return; \
+#define VK_CHECK(expr) \
+ if (VkResult result = (expr); result != VK_SUCCESS) { \
+ BAIL("[%s] failed. err = %d", #expr, result); \
+ return; \
}
#define VK_GET_PROC(F) \
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
index 69f583226b..7a72d09804 100644
--- a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
@@ -110,8 +110,40 @@ bool GraphiteGpuContext::isAbandonedOrDeviceLost() {
return mContext->isDeviceLost();
}
+void GraphiteGpuContext::setResourceCacheLimit(size_t maxResourceBytes) {
+ // Graphite has a separate budget for its Context and its Recorder. For now the majority of
+ // memory that Graphite will allocate will be on the Recorder and minimal amount on the Context.
+ // The main allocations on the Context are MSAA buffers (not often, if ever used in
+ // RenderEngine) and stencil buffers. However, both of these should be "memoryless" in Vulkan on
+ // tiled GPUs, so they don't actually use GPU memory. However, in Vulkan there are scenarios
+ // where Vulkan could end up using real memory for them. Skia will regularly query the device to
+ // get the real memory usage and update the budgeted appropriately. Though for all real usage
+ // patterns we don't expect to ever trigger the device to allocate real memory.
+ //
+ // Therefore, we set the full maxResourceBytes budget on the Recorder. However, in the rare
+ // chance that the devcies does allocate real memory we don't want to immediately kill device
+ // performance by constantly trashing allocations on the Context. Thus we set the Context's
+ // budget to be 50% of the total budget to make sure we allow the MSAA or Stencil buffers to be
+ // allocated in Skia and not immediately discarded. But even with this extra 50% budget, as
+ // described above, this shouldn't result in actual GPU memory usage.
+ //
+ // TODO: We will need to revise this strategy for GLES which does not have the same memoryless
+ // textures.
+ // TODO: Work in Graphite has started to move a lot more of its scratch resources to be owned
+ // by the Context and not on Recorders. This will mean most memory is actually owned by the
+ // Context and thus the budgeting here will need to be updated.
+ mContext->setMaxBudgetedBytes(maxResourceBytes / 2);
+ mRecorder->setMaxBudgetedBytes(maxResourceBytes);
+}
+
+void GraphiteGpuContext::purgeUnlockedScratchResources() {
+ mContext->freeGpuResources();
+ mRecorder->freeGpuResources();
+}
+
void GraphiteGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
mContext->dumpMemoryStatistics(traceMemoryDump);
+ mRecorder->dumpMemoryStatistics(traceMemoryDump);
}
} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.h b/libs/renderengine/skia/compat/GraphiteGpuContext.h
index 413817ffff..57da796af5 100644
--- a/libs/renderengine/skia/compat/GraphiteGpuContext.h
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.h
@@ -39,16 +39,10 @@ public:
size_t getMaxRenderTargetSize() const override;
size_t getMaxTextureSize() const override;
bool isAbandonedOrDeviceLost() override;
- // No-op (large resources like textures, surfaces, images, etc. created by clients don't count
- // towards Graphite's internal caching budgets, so adjusting its limits based on display change
- // events should be unnecessary. Additionally, Graphite doesn't expose many cache tweaking
- // functions yet, as its design may evolve.)
- void setResourceCacheLimit(size_t maxResourceBytes) override{};
- // TODO: b/293371537 - Triple-check and validate that no cleanup is necessary when switching
- // contexts.
- // No-op (unnecessary during context switch for Graphite's client-budgeted memory model).
- void purgeUnlockedScratchResources() override{};
+ void setResourceCacheLimit(size_t maxResourceBytes) override;
+ void purgeUnlockedScratchResources() override;
+
// No-op (only applicable to GL).
void resetContextIfApplicable() override{};
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index cd1bd71807..8edf98eb89 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -90,6 +90,8 @@ void BlurFilter::drawBlurRegion(SkCanvas* canvas, const SkRRect& effectRegion,
linearSampling, &blurMatrix);
if (blurRadius < mMaxCrossFadeRadius) {
+ LOG_ALWAYS_FATAL_IF(!input);
+
// For sampling Skia's API expects the inverse of what logically seems appropriate. In this
// case you might expect the matrix to simply be the canvas matrix.
SkMatrix inputMatrix;
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index 8c52c571a9..b03ebe3353 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -64,7 +64,7 @@ sk_sp<SkImage> GaussianBlurFilter::generate(SkiaGpuContext* context, const uint3
SkSamplingOptions{SkFilterMode::kLinear, SkMipmapMode::kNone},
&paint,
SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint);
- return surface->makeImageSnapshot();
+ return surface->makeTemporaryImage();
}
} // namespace skia
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
index db0b133a26..ff96b08283 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
@@ -39,12 +39,36 @@ namespace renderengine {
namespace skia {
KawaseBlurDualFilter::KawaseBlurDualFilter() : BlurFilter() {
- // A shader to sample each vertex of a unit regular heptagon
- // plus the original fragment coordinate.
- SkString blurString(R"(
+ // A shader to sample each vertex of a square, plus the original fragment coordinate,
+ // using a total of 5 samples.
+ SkString lowSampleBlurString(R"(
uniform shader child;
uniform float in_blurOffset;
uniform float in_crossFade;
+ uniform float in_weightedCrossFade;
+
+ const float2 STEP_0 = float2( 0.707106781, 0.707106781);
+ const float2 STEP_1 = float2( 0.707106781, -0.707106781);
+ const float2 STEP_2 = float2(-0.707106781, -0.707106781);
+ const float2 STEP_3 = float2(-0.707106781, 0.707106781);
+
+ half4 main(float2 xy) {
+ half3 c = child.eval(xy).rgb;
+
+ c += child.eval(xy + STEP_0 * in_blurOffset).rgb;
+ c += child.eval(xy + STEP_1 * in_blurOffset).rgb;
+ c += child.eval(xy + STEP_2 * in_blurOffset).rgb;
+ c += child.eval(xy + STEP_3 * in_blurOffset).rgb;
+
+ return half4(c * in_weightedCrossFade, in_crossFade);
+ }
+ )");
+
+ // A shader to sample each vertex of a unit regular heptagon, plus the original fragment
+ // coordinate, using a total of 8 samples.
+ SkString highSampleBlurString(R"(
+ uniform shader child;
+ uniform float in_blurOffset;
const float2 STEP_0 = float2( 1.0, 0.0);
const float2 STEP_1 = float2( 0.623489802, 0.781831482);
@@ -65,44 +89,48 @@ KawaseBlurDualFilter::KawaseBlurDualFilter() : BlurFilter() {
c += child.eval(xy + STEP_5 * in_blurOffset).rgb;
c += child.eval(xy + STEP_6 * in_blurOffset).rgb;
- return half4(c * 0.125 * in_crossFade, in_crossFade);
+ return half4(c * 0.125, 1.0);
}
)");
- auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
- LOG_ALWAYS_FATAL_IF(!blurEffect, "RuntimeShader error: %s", error.c_str());
- mBlurEffect = std::move(blurEffect);
-}
-
-static sk_sp<SkSurface> makeSurface(SkiaGpuContext* context, const SkRect& origRect, int scale) {
- SkImageInfo scaledInfo =
- SkImageInfo::MakeN32Premul(ceil(static_cast<float>(origRect.width()) / scale),
- ceil(static_cast<float>(origRect.height()) / scale));
- return context->createRenderTarget(scaledInfo);
+ auto [lowSampleBlurEffect, error] = SkRuntimeEffect::MakeForShader(lowSampleBlurString);
+ auto [highSampleBlurEffect, error2] = SkRuntimeEffect::MakeForShader(highSampleBlurString);
+ LOG_ALWAYS_FATAL_IF(!lowSampleBlurEffect, "RuntimeShader error: %s", error.c_str());
+ LOG_ALWAYS_FATAL_IF(!highSampleBlurEffect, "RuntimeShader error: %s", error2.c_str());
+ mLowSampleBlurEffect = std::move(lowSampleBlurEffect);
+ mHighSampleBlurEffect = std::move(highSampleBlurEffect);
}
void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface,
const sk_sp<SkImage>& readImage, const float radius,
- const float alpha) const {
+ const float alpha,
+ const sk_sp<SkRuntimeEffect>& blurEffect) const {
const float scale = static_cast<float>(drawSurface->width()) / readImage->width();
SkMatrix blurMatrix = SkMatrix::Scale(scale, scale);
blurInto(drawSurface,
readImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
blurMatrix),
- readImage->width() / static_cast<float>(drawSurface->width()), radius, alpha);
+ radius, alpha, blurEffect);
}
void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface, sk_sp<SkShader> input,
- const float inverseScale, const float radius,
- const float alpha) const {
- SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
- blurBuilder.child("child") = std::move(input);
- blurBuilder.uniform("in_inverseScale") = inverseScale;
- blurBuilder.uniform("in_blurOffset") = radius;
- blurBuilder.uniform("in_crossFade") = alpha;
+ const float radius, const float alpha,
+ const sk_sp<SkRuntimeEffect>& blurEffect) const {
SkPaint paint;
- paint.setShader(blurBuilder.makeShader(nullptr));
+ if (radius == 0) {
+ paint.setShader(std::move(input));
+ paint.setAlphaf(alpha);
+ } else {
+ SkRuntimeShaderBuilder blurBuilder(blurEffect);
+ blurBuilder.child("child") = std::move(input);
+ if (blurEffect == mLowSampleBlurEffect) {
+ blurBuilder.uniform("in_crossFade") = alpha;
+ blurBuilder.uniform("in_weightedCrossFade") = alpha * 0.2f;
+ }
+ blurBuilder.uniform("in_blurOffset") = radius;
+ paint.setShader(blurBuilder.makeShader(nullptr));
+ }
paint.setBlendMode(alpha == 1.0f ? SkBlendMode::kSrc : SkBlendMode::kSrcOver);
drawSurface->getCanvas()->drawPaint(paint);
}
@@ -116,32 +144,44 @@ sk_sp<SkImage> KawaseBlurDualFilter::generate(SkiaGpuContext* context, const uin
// Use a variable number of blur passes depending on the radius. The non-integer part of this
// calculation is used to mix the final pass into the second-last with an alpha blend.
- constexpr int kMaxSurfaces = 4;
- const float filterDepth =
- std::min(kMaxSurfaces - 1.0f, 1.0f + std::max(0.0f, log2f(radius * kInputScale)));
+ constexpr int kMaxSurfaces = 3;
+ const float filterDepth = std::min(kMaxSurfaces - 1.0f, radius * kInputScale / 2.5f);
const int filterPasses = std::min(kMaxSurfaces - 1, static_cast<int>(ceil(filterDepth)));
- // Render into surfaces downscaled by 1x, 1x, 2x, and 4x from the initial downscale.
- sk_sp<SkSurface> surfaces[kMaxSurfaces] =
- {filterPasses >= 0 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr,
- filterPasses >= 1 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr,
- filterPasses >= 2 ? makeSurface(context, blurRect, 2 * kInverseInputScale) : nullptr,
- filterPasses >= 3 ? makeSurface(context, blurRect, 4 * kInverseInputScale) : nullptr};
+ auto makeSurface = [&](float scale) -> sk_sp<SkSurface> {
+ const auto newW = ceil(static_cast<float>(blurRect.width() / scale));
+ const auto newH = ceil(static_cast<float>(blurRect.height() / scale));
+ sk_sp<SkSurface> surface =
+ context->createRenderTarget(input->imageInfo().makeWH(newW, newH));
+ LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__);
+ return surface;
+ };
- // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 600.
- static const float kWeights[7] = {1.0f, 2.0f, 3.5f, 1.0f, 2.0f, 2.0f, 2.0f};
+ // Render into surfaces downscaled by 1x, 2x, and 4x from the initial downscale.
+ sk_sp<SkSurface> surfaces[kMaxSurfaces] =
+ {filterPasses >= 0 ? makeSurface(1 * kInverseInputScale) : nullptr,
+ filterPasses >= 1 ? makeSurface(2 * kInverseInputScale) : nullptr,
+ filterPasses >= 2 ? makeSurface(4 * kInverseInputScale) : nullptr};
+
+ // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 250.
+ static const float kWeights[5] = {
+ 1.0f, // 1st downsampling pass
+ 1.0f, // 2nd downsampling pass
+ 1.0f, // 3rd downsampling pass
+ 0.0f, // 1st upscaling pass. Set to zero to upscale without blurring for performance.
+ 1.0f, // 2nd upscaling pass
+ };
// Kawase is an approximation of Gaussian, but behaves differently because it is made up of many
// simpler blurs. A transformation is required to approximate the same effect as Gaussian.
- float sumSquaredR = powf(kWeights[0] * powf(2.0f, 1), 2.0f);
+ float sumSquaredR = powf(kWeights[0], 2.0f);
for (int i = 0; i < filterPasses; i++) {
const float alpha = std::min(1.0f, filterDepth - i);
- sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[1 + i], 2.0f);
- sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[6 - i], 2.0f);
+ sumSquaredR += powf(powf(2.0f, i) * alpha * kWeights[1 + i] / kInputScale, 2.0f);
+ sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[4 - i] / kInputScale, 2.0f);
}
- // Solve for R = sqrt(sum(r_i^2)). Divide R by hypot(1,1) to find some (x,y) offsets.
- const float step = M_SQRT1_2 *
- sqrtf(max(0.0f, (powf(radius, 2.0f) - powf(kInverseInputScale, 2.0f)) / sumSquaredR));
+ // Solve for R = sqrt(sum(r_i^2)).
+ const float step = radius * sqrt(1.0f / sumSquaredR);
// Start by downscaling and doing the first blur pass.
{
@@ -154,18 +194,21 @@ sk_sp<SkImage> KawaseBlurDualFilter::generate(SkiaGpuContext* context, const uin
input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
blurMatrix);
- blurInto(surfaces[0], std::move(sourceShader), kInputScale, kWeights[0] * step, 1.0f);
+ blurInto(surfaces[0], std::move(sourceShader), kWeights[0] * step, 1.0f,
+ mLowSampleBlurEffect);
}
// Next the remaining downscale blur passes.
for (int i = 0; i < filterPasses; i++) {
- blurInto(surfaces[i + 1], surfaces[i]->makeImageSnapshot(), kWeights[1 + i] * step, 1.0f);
+ // Blur with the higher sample effect into the smaller buffers, for better visual quality.
+ blurInto(surfaces[i + 1], surfaces[i]->makeTemporaryImage(), kWeights[1 + i] * step, 1.0f,
+ i == 0 ? mLowSampleBlurEffect : mHighSampleBlurEffect);
}
// Finally blur+upscale back to our original size.
for (int i = filterPasses - 1; i >= 0; i--) {
- blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[6 - i] * step,
- std::min(1.0f, filterDepth - i));
+ blurInto(surfaces[i], surfaces[i + 1]->makeTemporaryImage(), kWeights[4 - i] * step,
+ std::min(1.0f, filterDepth - i), mLowSampleBlurEffect);
}
- return surfaces[0]->makeImageSnapshot();
+ return surfaces[0]->makeTemporaryImage();
}
} // namespace skia
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.h b/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
index 6f4adbf34c..5efda35376 100644
--- a/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
@@ -41,13 +41,14 @@ public:
const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
private:
- sk_sp<SkRuntimeEffect> mBlurEffect;
+ sk_sp<SkRuntimeEffect> mLowSampleBlurEffect;
+ sk_sp<SkRuntimeEffect> mHighSampleBlurEffect;
void blurInto(const sk_sp<SkSurface>& drawSurface, const sk_sp<SkImage>& readImage,
- const float radius, const float alpha) const;
+ const float radius, const float alpha, const sk_sp<SkRuntimeEffect>&) const;
void blurInto(const sk_sp<SkSurface>& drawSurface, const sk_sp<SkShader> input,
- const float inverseScale, const float radius, const float alpha) const;
+ const float radius, const float alpha, const sk_sp<SkRuntimeEffect>&) const;
};
} // namespace skia
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
index defaf6e476..f71a63d591 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -70,7 +70,7 @@ static sk_sp<SkImage> makeImage(SkSurface* surface, SkRuntimeShaderBuilder* buil
paint.setShader(std::move(shader));
paint.setBlendMode(SkBlendMode::kSrc);
surface->getCanvas()->drawPaint(paint);
- return surface->makeImageSnapshot();
+ return surface->makeTemporaryImage();
}
sk_sp<SkImage> KawaseBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
diff --git a/libs/renderengine/skia/filters/LutShader.cpp b/libs/renderengine/skia/filters/LutShader.cpp
new file mode 100644
index 0000000000..f262158f2c
--- /dev/null
+++ b/libs/renderengine/skia/filters/LutShader.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2024 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 "LutShader.h"
+
+#include <SkM44.h>
+#include <SkTileMode.h>
+#include <common/trace.h>
+#include <cutils/ashmem.h>
+#include <math/half.h>
+#include <sys/mman.h>
+#include <ui/ColorSpace.h>
+
+#include "include/core/SkColorSpace.h"
+#include "src/core/SkColorFilterPriv.h"
+
+using aidl::android::hardware::graphics::composer3::LutProperties;
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+static const SkString kShader = SkString(R"(
+ uniform shader image;
+ uniform shader lut;
+ uniform int size;
+ uniform int key;
+ uniform int dimension;
+ uniform vec3 luminanceCoefficients; // for CIE_Y
+ // for hlg/pq transfer function, we need normalize it to [0.0, 1.0]
+ // we use `normalizeScalar` to do so
+ uniform float normalizeScalar;
+
+ vec4 main(vec2 xy) {
+ float4 rgba = image.eval(xy);
+ float3 linear = toLinearSrgb(rgba.rgb) * normalizeScalar;
+ if (dimension == 1) {
+ // RGB
+ if (key == 0) {
+ float indexR = linear.r * float(size - 1);
+ float indexG = linear.g * float(size - 1);
+ float indexB = linear.b * float(size - 1);
+ float gainR = lut.eval(vec2(indexR, 0.0) + 0.5).r;
+ float gainG = lut.eval(vec2(indexG, 0.0) + 0.5).r;
+ float gainB = lut.eval(vec2(indexB, 0.0) + 0.5).r;
+ linear = float3(linear.r * gainR, linear.g * gainG, linear.b * gainB);
+ // MAX_RGB
+ } else if (key == 1) {
+ float maxRGB = max(linear.r, max(linear.g, linear.b));
+ float index = maxRGB * float(size - 1);
+ float gain = lut.eval(vec2(index, 0.0) + 0.5).r;
+ linear = linear * gain;
+ // CIE_Y
+ } else if (key == 2) {
+ float y = dot(linear, luminanceCoefficients) / 3.0;
+ float index = y * float(size - 1);
+ float gain = lut.eval(vec2(index, 0.0) + 0.5).r;
+ linear = linear * gain;
+ }
+ } else if (dimension == 3) {
+ if (key == 0) {
+ float tx = linear.r * float(size - 1);
+ float ty = linear.g * float(size - 1);
+ float tz = linear.b * float(size - 1);
+
+ // calculate lower and upper bounds for each dimension
+ int x = int(tx);
+ int y = int(ty);
+ int z = int(tz);
+
+ int i000 = x + y * size + z * size * size;
+ int i100 = i000 + 1;
+ int i010 = i000 + size;
+ int i110 = i000 + size + 1;
+ int i001 = i000 + size * size;
+ int i101 = i000 + size * size + 1;
+ int i011 = i000 + size * size + size;
+ int i111 = i000 + size * size + size + 1;
+
+ // get 1d normalized indices
+ float c000 = float(i000) / float(size * size * size);
+ float c100 = float(i100) / float(size * size * size);
+ float c010 = float(i010) / float(size * size * size);
+ float c110 = float(i110) / float(size * size * size);
+ float c001 = float(i001) / float(size * size * size);
+ float c101 = float(i101) / float(size * size * size);
+ float c011 = float(i011) / float(size * size * size);
+ float c111 = float(i111) / float(size * size * size);
+
+ //TODO(b/377984618): support Tetrahedral interpolation
+ // perform trilinear interpolation
+ float3 c00 = mix(lut.eval(vec2(c000, 0.0) + 0.5).rgb,
+ lut.eval(vec2(c100, 0.0) + 0.5).rgb, linear.r);
+ float3 c01 = mix(lut.eval(vec2(c001, 0.0) + 0.5).rgb,
+ lut.eval(vec2(c101, 0.0) + 0.5).rgb, linear.r);
+ float3 c10 = mix(lut.eval(vec2(c010, 0.0) + 0.5).rgb,
+ lut.eval(vec2(c110, 0.0) + 0.5).rgb, linear.r);
+ float3 c11 = mix(lut.eval(vec2(c011, 0.0) + 0.5).rgb,
+ lut.eval(vec2(c111, 0.0) + 0.5).rgb, linear.r);
+
+ float3 c0 = mix(c00, c10, linear.g);
+ float3 c1 = mix(c01, c11, linear.g);
+
+ linear = mix(c0, c1, linear.b);
+ }
+ }
+ return float4(linear, rgba.a);
+ })");
+
+// same as shader::toColorSpace function
+// TODO: put this function in a general place
+static ColorSpace toColorSpace(ui::Dataspace dataspace) {
+ switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
+ case HAL_DATASPACE_STANDARD_BT709:
+ return ColorSpace::sRGB();
+ case HAL_DATASPACE_STANDARD_DCI_P3:
+ return ColorSpace::DisplayP3();
+ case HAL_DATASPACE_STANDARD_BT2020:
+ case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE:
+ return ColorSpace::BT2020();
+ case HAL_DATASPACE_STANDARD_ADOBE_RGB:
+ return ColorSpace::AdobeRGB();
+ case HAL_DATASPACE_STANDARD_BT601_625:
+ case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED:
+ case HAL_DATASPACE_STANDARD_BT601_525:
+ case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED:
+ case HAL_DATASPACE_STANDARD_BT470M:
+ case HAL_DATASPACE_STANDARD_FILM:
+ case HAL_DATASPACE_STANDARD_UNSPECIFIED:
+ default:
+ return ColorSpace::sRGB();
+ }
+}
+
+sk_sp<SkShader> LutShader::generateLutShader(sk_sp<SkShader> input,
+ const std::vector<float>& buffers,
+ const int32_t offset, const int32_t length,
+ const int32_t dimension, const int32_t size,
+ const int32_t samplingKey,
+ ui::Dataspace srcDataspace) {
+ SFTRACE_NAME("lut shader");
+ std::vector<half> buffer(length * 4); // 4 is for RGBA
+ auto d = static_cast<LutProperties::Dimension>(dimension);
+ if (d == LutProperties::Dimension::ONE_D) {
+ auto it = buffers.begin() + offset;
+ std::generate(buffer.begin(), buffer.end(), [it, i = 0]() mutable {
+ float val = (i++ % 4 == 0) ? *it++ : 0.0f;
+ return half(val);
+ });
+ } else {
+ for (int i = 0; i < length; i++) {
+ buffer[i * 4] = half(buffers[offset + i]);
+ buffer[i * 4 + 1] = half(buffers[offset + length + i]);
+ buffer[i * 4 + 2] = half(buffers[offset + length * 2 + i]);
+ buffer[i * 4 + 3] = half(0);
+ }
+ }
+ /**
+ * 1D Lut RGB/MAX_RGB
+ * (R0, 0, 0, 0)
+ * (R1, 0, 0, 0)
+ *
+ * 1D Lut CIE_Y
+ * (Y0, 0, 0, 0)
+ * (Y1, 0, 0, 0)
+ * ...
+ *
+ * 3D Lut MAX_RGB
+ * (R0, G0, B0, 0)
+ * (R1, G1, B1, 0)
+ * ...
+ */
+ SkImageInfo info = SkImageInfo::Make(length /* the number of rgba */, 1, kRGBA_F16_SkColorType,
+ kPremul_SkAlphaType);
+ SkBitmap bitmap;
+ bitmap.allocPixels(info);
+ if (!bitmap.installPixels(info, buffer.data(), info.minRowBytes())) {
+ ALOGW("bitmap.installPixels failed, skip this Lut!");
+ return input;
+ }
+
+ sk_sp<SkImage> lutImage = SkImages::RasterFromBitmap(bitmap);
+ if (!lutImage) {
+ ALOGW("Got a nullptr from SkImages::RasterFromBitmap, skip this Lut!");
+ return input;
+ }
+
+ mBuilder->child("image") = input;
+ mBuilder->child("lut") =
+ lutImage->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
+ d == LutProperties::Dimension::ONE_D
+ ? SkSamplingOptions(SkFilterMode::kLinear)
+ : SkSamplingOptions());
+
+ float normalizeScalar = 1.0;
+ switch (srcDataspace & HAL_DATASPACE_TRANSFER_MASK) {
+ case HAL_DATASPACE_TRANSFER_HLG:
+ normalizeScalar = 0.203;
+ break;
+ case HAL_DATASPACE_TRANSFER_ST2084:
+ normalizeScalar = 0.0203;
+ break;
+ default:
+ normalizeScalar = 1.0;
+ }
+ const int uSize = static_cast<int>(size);
+ const int uKey = static_cast<int>(samplingKey);
+ const int uDimension = static_cast<int>(dimension);
+ const float uNormalizeScalar = static_cast<float>(normalizeScalar);
+
+ if (static_cast<LutProperties::SamplingKey>(samplingKey) == LutProperties::SamplingKey::CIE_Y) {
+ // Use predefined colorspaces of input dataspace so that we can get D65 illuminant
+ mat3 toXYZMatrix(toColorSpace(srcDataspace).getRGBtoXYZ());
+ mBuilder->uniform("luminanceCoefficients") =
+ SkV3{toXYZMatrix[0][1], toXYZMatrix[1][1], toXYZMatrix[2][1]};
+ } else {
+ mBuilder->uniform("luminanceCoefficients") = SkV3{1.f, 1.f, 1.f};
+ }
+ mBuilder->uniform("size") = uSize;
+ mBuilder->uniform("key") = uKey;
+ mBuilder->uniform("dimension") = uDimension;
+ mBuilder->uniform("normalizeScalar") = uNormalizeScalar;
+ return mBuilder->makeShader();
+}
+
+sk_sp<SkShader> LutShader::lutShader(sk_sp<SkShader>& input,
+ std::shared_ptr<gui::DisplayLuts> displayLuts,
+ ui::Dataspace srcDataspace,
+ sk_sp<SkColorSpace> outColorSpace) {
+ if (mBuilder == nullptr) {
+ const static SkRuntimeEffect::Result instance = SkRuntimeEffect::MakeForShader(kShader);
+ mBuilder = std::make_unique<SkRuntimeShaderBuilder>(instance.effect);
+ }
+
+ auto& fd = displayLuts->getLutFileDescriptor();
+ if (fd.ok()) {
+ // de-gamma the image without changing the primaries
+ SkImage* baseImage = input->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr);
+ sk_sp<SkColorSpace> baseColorSpace = baseImage && baseImage->colorSpace()
+ ? baseImage->refColorSpace()
+ : SkColorSpace::MakeSRGB();
+ sk_sp<SkColorSpace> lutMathColorSpace = baseColorSpace->makeLinearGamma();
+ input = input->makeWithWorkingColorSpace(lutMathColorSpace);
+
+ auto& offsets = displayLuts->offsets;
+ auto& lutProperties = displayLuts->lutProperties;
+ std::vector<float> buffers;
+ int fullLength = offsets[lutProperties.size() - 1];
+ if (lutProperties[lutProperties.size() - 1].dimension == 1) {
+ fullLength += lutProperties[lutProperties.size() - 1].size;
+ } else {
+ fullLength += (lutProperties[lutProperties.size() - 1].size *
+ lutProperties[lutProperties.size() - 1].size *
+ lutProperties[lutProperties.size() - 1].size * 3);
+ }
+ size_t bufferSize = fullLength * sizeof(float);
+
+ // decode the shared memory of luts
+ float* ptr =
+ (float*)mmap(NULL, bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0);
+ if (ptr == MAP_FAILED) {
+ LOG_ALWAYS_FATAL("mmap failed");
+ }
+ buffers = std::vector<float>(ptr, ptr + fullLength);
+ munmap(ptr, bufferSize);
+
+ for (size_t i = 0; i < offsets.size(); i++) {
+ int bufferSizePerLut = (i == offsets.size() - 1) ? buffers.size() - offsets[i]
+ : offsets[i + 1] - offsets[i];
+ // divide by 3 for 3d Lut because of 3 (RGB) channels
+ if (static_cast<LutProperties::Dimension>(lutProperties[i].dimension) ==
+ LutProperties::Dimension::THREE_D) {
+ bufferSizePerLut /= 3;
+ }
+ input = generateLutShader(input, buffers, offsets[i], bufferSizePerLut,
+ lutProperties[i].dimension, lutProperties[i].size,
+ lutProperties[i].samplingKey, srcDataspace);
+ }
+
+ auto colorXformLutToDst =
+ SkColorFilterPriv::MakeColorSpaceXform(lutMathColorSpace, outColorSpace);
+ input = input->makeWithColorFilter(colorXformLutToDst);
+ }
+ return input;
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android \ No newline at end of file
diff --git a/libs/renderengine/skia/filters/LutShader.h b/libs/renderengine/skia/filters/LutShader.h
new file mode 100644
index 0000000000..7c62fcae08
--- /dev/null
+++ b/libs/renderengine/skia/filters/LutShader.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 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 <SkBitmap.h>
+#include <SkImage.h>
+#include <SkRuntimeEffect.h>
+
+#include <aidl/android/hardware/graphics/composer3/LutProperties.h>
+#include <gui/DisplayLuts.h>
+#include <ui/GraphicTypes.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+class LutShader {
+public:
+ sk_sp<SkShader> lutShader(sk_sp<SkShader>& input, std::shared_ptr<gui::DisplayLuts> displayLuts,
+ ui::Dataspace srcDataspace, sk_sp<SkColorSpace> outColorSpace);
+
+private:
+ sk_sp<SkShader> generateLutShader(sk_sp<SkShader> input, const std::vector<float>& buffers,
+ const int32_t offset, const int32_t length,
+ const int32_t dimension, const int32_t size,
+ const int32_t samplingKey, ui::Dataspace srcDataspace);
+ std::unique_ptr<SkRuntimeShaderBuilder> mBuilder;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/MouriMap.cpp b/libs/renderengine/skia/filters/MouriMap.cpp
index b099bcf3d7..5dc36e6358 100644
--- a/libs/renderengine/skia/filters/MouriMap.cpp
+++ b/libs/renderengine/skia/filters/MouriMap.cpp
@@ -30,12 +30,12 @@ sk_sp<SkRuntimeEffect> makeEffect(const SkString& sksl) {
}
const SkString kCrosstalkAndChunk16x16(R"(
uniform shader bitmap;
- uniform float hdrSdrRatio;
+ uniform float inputMultiplier;
vec4 main(vec2 xy) {
float maximum = 0.0;
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
- float3 linear = toLinearSrgb(bitmap.eval((xy - 0.5) * 16 + 0.5 + vec2(x, y)).rgb) * hdrSdrRatio;
+ float3 linear = toLinearSrgb(bitmap.eval((xy - 0.5) * 16 + 0.5 + vec2(x, y)).rgb) * inputMultiplier;
float maxRGB = max(linear.r, max(linear.g, linear.b));
maximum = max(maximum, log2(max(maxRGB, 1.0)));
}
@@ -77,12 +77,12 @@ const SkString kTonemap(R"(
uniform shader image;
uniform shader lux;
uniform float scaleFactor;
- uniform float hdrSdrRatio;
+ uniform float inputMultiplier;
uniform float targetHdrSdrRatio;
vec4 main(vec2 xy) {
float localMax = lux.eval(xy * scaleFactor).r;
float4 rgba = image.eval(xy);
- float3 linear = toLinearSrgb(rgba.rgb) * hdrSdrRatio;
+ float3 linear = toLinearSrgb(rgba.rgb) * inputMultiplier;
if (localMax <= targetHdrSdrRatio) {
return float4(fromLinearSrgb(linear), rgba.a);
@@ -104,7 +104,7 @@ sk_sp<SkImage> makeImage(SkSurface* surface, const SkRuntimeShaderBuilder& build
paint.setShader(std::move(shader));
paint.setBlendMode(SkBlendMode::kSrc);
surface->getCanvas()->drawPaint(paint);
- return surface->makeImageSnapshot();
+ return surface->makeTemporaryImage();
}
} // namespace
@@ -116,19 +116,19 @@ MouriMap::MouriMap()
mTonemap(makeEffect(kTonemap)) {}
sk_sp<SkShader> MouriMap::mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input,
- float hdrSdrRatio, float targetHdrSdrRatio) {
- auto downchunked = downchunk(context, input, hdrSdrRatio);
+ float inputMultiplier, float targetHdrSdrRatio) {
+ auto downchunked = downchunk(context, input, inputMultiplier);
auto localLux = blur(context, downchunked.get());
- return tonemap(input, localLux.get(), hdrSdrRatio, targetHdrSdrRatio);
+ return tonemap(input, localLux.get(), inputMultiplier, targetHdrSdrRatio);
}
sk_sp<SkImage> MouriMap::downchunk(SkiaGpuContext* context, sk_sp<SkShader> input,
- float hdrSdrRatio) const {
+ float inputMultiplier) const {
SkMatrix matrix;
SkImage* image = input->isAImage(&matrix, (SkTileMode*)nullptr);
SkRuntimeShaderBuilder crosstalkAndChunk16x16Builder(mCrosstalkAndChunk16x16);
crosstalkAndChunk16x16Builder.child("bitmap") = input;
- crosstalkAndChunk16x16Builder.uniform("hdrSdrRatio") = hdrSdrRatio;
+ crosstalkAndChunk16x16Builder.uniform("inputMultiplier") = inputMultiplier;
// TODO: fp16 might be overkill. Most practical surfaces use 8-bit RGB for HDR UI and 10-bit YUV
// for HDR video. These downsample operations compute log2(max(linear RGB, 1.0)). So we don't
// care about LDR precision since they all resolve to LDR-max. For appropriately mastered HDR
@@ -168,7 +168,7 @@ sk_sp<SkImage> MouriMap::blur(SkiaGpuContext* context, SkImage* input) const {
LOG_ALWAYS_FATAL_IF(!blurSurface, "%s: Failed to create surface!", __func__);
return makeImage(blurSurface.get(), blurBuilder);
}
-sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux, float hdrSdrRatio,
+sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux, float inputMultiplier,
float targetHdrSdrRatio) const {
static constexpr float kScaleFactor = 1.0f / 128.0f;
SkRuntimeShaderBuilder tonemapBuilder(mTonemap);
@@ -177,7 +177,7 @@ sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux, floa
localLux->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone));
tonemapBuilder.uniform("scaleFactor") = kScaleFactor;
- tonemapBuilder.uniform("hdrSdrRatio") = hdrSdrRatio;
+ tonemapBuilder.uniform("inputMultiplier") = inputMultiplier;
tonemapBuilder.uniform("targetHdrSdrRatio") = targetHdrSdrRatio;
return tonemapBuilder.makeShader();
}
diff --git a/libs/renderengine/skia/filters/MouriMap.h b/libs/renderengine/skia/filters/MouriMap.h
index 9ba2b6ff9d..f4bfa1549e 100644
--- a/libs/renderengine/skia/filters/MouriMap.h
+++ b/libs/renderengine/skia/filters/MouriMap.h
@@ -62,10 +62,13 @@ class MouriMap {
public:
MouriMap();
// Apply the MouriMap tonemmaping operator to the input.
- // The HDR/SDR ratio describes the luminace range of the input. 1.0 means SDR. Anything larger
- // then 1.0 means that there is headroom above the SDR region.
- // Similarly, the target HDR/SDR ratio describes the luminance range of the output.
- sk_sp<SkShader> mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, float inputHdrSdrRatio,
+ // The inputMultiplier informs how to interpret the luminance encoding of the input.
+ // For a fixed point input, this is necessary to interpret what "1.0" means for the input
+ // pixels, so that the luminance can be scaled appropriately, such that the operator can
+ // transform SDR values to be within 1.0. For a floating point input, "1.0" always means SDR,
+ // so the caller must pass 1.0.
+ // The target HDR/SDR ratio describes the luminance range of the output.
+ sk_sp<SkShader> mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, float inputMultiplier,
float targetHdrSdrRatio);
private:
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index c187f93089..67f4aa10f8 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -23,6 +23,7 @@
#include <future>
#include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
#include <common/trace.h>
#include <private/gui/SyncFeatures.h>
#include <processgroup/processgroup.h>
@@ -60,7 +61,7 @@ status_t RenderEngineThreaded::setSchedFifo(bool enabled) {
struct sched_param param = {0};
int sched_policy;
- if (enabled) {
+ if (enabled && !FlagManager::getInstance().disable_sched_fifo_re()) {
sched_policy = SCHED_FIFO;
param.sched_priority = kFifoPriority;
} else {
@@ -249,11 +250,10 @@ void RenderEngineThreaded::drawLayersInternal(
return;
}
-void RenderEngineThreaded::drawGainmapInternal(
+void RenderEngineThreaded::tonemapAndDrawGainmapInternal(
const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
- const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
- float hdrSdrRatio, ui::Dataspace dataspace,
+ float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
const std::shared_ptr<ExternalTexture>& gainmap) {
resultPromise->set_value(Fence::NO_FENCE);
return;
@@ -281,10 +281,9 @@ ftl::Future<FenceResult> RenderEngineThreaded::drawLayers(
return resultFuture;
}
-ftl::Future<FenceResult> RenderEngineThreaded::drawGainmap(
- const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+ftl::Future<FenceResult> RenderEngineThreaded::tonemapAndDrawGainmap(
const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
- float hdrSdrRatio, ui::Dataspace dataspace,
+ float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
const std::shared_ptr<ExternalTexture>& gainmap) {
SFTRACE_CALL();
const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
@@ -292,13 +291,14 @@ ftl::Future<FenceResult> RenderEngineThreaded::drawGainmap(
{
std::lock_guard lock(mThreadMutex);
mNeedsPostRenderCleanup = true;
- mFunctionCalls.push([resultPromise, sdr, sdrFence = std::move(sdrFence), hdr,
- hdrFence = std::move(hdrFence), hdrSdrRatio, dataspace,
+ mFunctionCalls.push([resultPromise, hdr, hdrFence = std::move(hdrFence), hdrSdrRatio,
+ dataspace, sdr,
gainmap](renderengine::RenderEngine& instance) mutable {
- SFTRACE_NAME("REThreaded::drawGainmap");
- instance.updateProtectedContext({}, {sdr.get(), hdr.get(), gainmap.get()});
- instance.drawGainmapInternal(std::move(resultPromise), sdr, std::move(sdrFence), hdr,
- std::move(hdrFence), hdrSdrRatio, dataspace, gainmap);
+ SFTRACE_NAME("REThreaded::tonemapAndDrawGainmap");
+ instance.updateProtectedContext({}, {hdr.get(), sdr.get(), gainmap.get()});
+ instance.tonemapAndDrawGainmapInternal(std::move(resultPromise), hdr,
+ std::move(hdrFence), hdrSdrRatio, dataspace, sdr,
+ gainmap);
});
}
mCondition.notify_one();
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index cb6e924d81..8554b55030 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -55,12 +55,10 @@ public:
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
base::unique_fd&& bufferFence) override;
- ftl::Future<FenceResult> drawGainmap(const std::shared_ptr<ExternalTexture>& sdr,
- base::borrowed_fd&& sdrFence,
- const std::shared_ptr<ExternalTexture>& hdr,
- base::borrowed_fd&& hdrFence, float hdrSdrRatio,
- ui::Dataspace dataspace,
- const std::shared_ptr<ExternalTexture>& gainmap) override;
+ ftl::Future<FenceResult> tonemapAndDrawGainmap(
+ const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+ float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
+ const std::shared_ptr<ExternalTexture>& gainmap) override;
int getContextPriority() override;
bool supportsBackgroundBlur() override;
@@ -77,13 +75,11 @@ protected:
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
base::unique_fd&& bufferFence) override;
- void drawGainmapInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
- const std::shared_ptr<ExternalTexture>& sdr,
- base::borrowed_fd&& sdrFence,
- const std::shared_ptr<ExternalTexture>& hdr,
- base::borrowed_fd&& hdrFence, float hdrSdrRatio,
- ui::Dataspace dataspace,
- const std::shared_ptr<ExternalTexture>& gainmap) override;
+ void tonemapAndDrawGainmapInternal(
+ const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+ const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+ float hdrSdrRatio, ui::Dataspace dataspace, const std::shared_ptr<ExternalTexture>& sdr,
+ const std::shared_ptr<ExternalTexture>& gainmap) override;
private:
void threadMain(CreateInstanceFactory factory);
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index 659666d6b6..a687a37499 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -69,6 +69,7 @@ cc_library {
static_libs: [
"libsensor_flags_c_lib",
+ "android.permission.flags-aconfig-cc",
],
export_include_dirs: ["include"],
diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp
index a1549ea385..797efbe5df 100644
--- a/libs/sensor/Sensor.cpp
+++ b/libs/sensor/Sensor.cpp
@@ -21,6 +21,7 @@
#include <binder/AppOpsManager.h>
#include <binder/IPermissionController.h>
#include <binder/IServiceManager.h>
+#include <android_permission_flags.h>
/*
* The permission to use for activity recognition sensors (like step counter).
@@ -121,7 +122,9 @@ Sensor::Sensor(struct sensor_t const& hwSensor, const uuid_t& uuid, int halVersi
break;
case SENSOR_TYPE_HEART_RATE: {
mStringType = SENSOR_STRING_TYPE_HEART_RATE;
- mRequiredPermission = SENSOR_PERMISSION_BODY_SENSORS;
+ mRequiredPermission =
+ android::permission::flags::replace_body_sensor_permission_enabled() ?
+ SENSOR_PERMISSION_READ_HEART_RATE : SENSOR_PERMISSION_BODY_SENSORS;
AppOpsManager appOps;
mRequiredAppOp = appOps.permissionToOpCode(String16(mRequiredPermission));
mFlags |= SENSOR_FLAG_ON_CHANGE_MODE;
@@ -303,7 +306,18 @@ Sensor::Sensor(struct sensor_t const& hwSensor, const uuid_t& uuid, int halVersi
}
if (halVersion > SENSORS_DEVICE_API_VERSION_1_0 && hwSensor.requiredPermission) {
mRequiredPermission = hwSensor.requiredPermission;
- if (!strcmp(mRequiredPermission, SENSOR_PERMISSION_BODY_SENSORS)) {
+ bool requiresBodySensorPermission =
+ !strcmp(mRequiredPermission, SENSOR_PERMISSION_BODY_SENSORS);
+ if (android::permission::flags::replace_body_sensor_permission_enabled()) {
+ if (requiresBodySensorPermission) {
+ ALOGE("Sensor %s using deprecated Body Sensor permission", mName.c_str());
+ }
+
+ AppOpsManager appOps;
+ // Lookup to see if an AppOp exists for the permission. If none
+ // does, the default value of -1 is used.
+ mRequiredAppOp = appOps.permissionToOpCode(String16(mRequiredPermission));
+ } else if (requiresBodySensorPermission) {
AppOpsManager appOps;
mRequiredAppOp = appOps.permissionToOpCode(String16(SENSOR_PERMISSION_BODY_SENSORS));
}
diff --git a/libs/tracing_perfetto/Android.bp b/libs/tracing_perfetto/Android.bp
index b5c56c5c52..1ef83a4706 100644
--- a/libs/tracing_perfetto/Android.bp
+++ b/libs/tracing_perfetto/Android.bp
@@ -21,7 +21,7 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
-cc_library_shared {
+cc_library {
name: "libtracing_perfetto",
export_include_dirs: [
"include",
@@ -37,14 +37,20 @@ cc_library_shared {
srcs: [
"tracing_perfetto.cpp",
"tracing_perfetto_internal.cpp",
+ "tracing_sdk.cpp",
],
shared_libs: [
"libbase",
"libcutils",
"libperfetto_c",
- "android.os.flags-aconfig-cc-host",
+ ],
+
+ export_shared_lib_headers: [
+ "libperfetto_c",
],
host_supported: true,
+ // for vndbinder
+ vendor_available: true,
}
diff --git a/libs/tracing_perfetto/include/tracing_sdk.h b/libs/tracing_perfetto/include/tracing_sdk.h
new file mode 100644
index 0000000000..271d7c8563
--- /dev/null
+++ b/libs/tracing_perfetto/include/tracing_sdk.h
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2024 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 <stdint.h>
+
+#include <optional>
+#include <vector>
+
+#include "perfetto/public/producer.h"
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#include "perfetto/public/track_event.h"
+
+#include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/pb_utils.h"
+#include "perfetto/public/tracing_session.h"
+
+/**
+ * The objects declared here are intended to be managed by Java.
+ * This means the Java Garbage Collector is responsible for freeing the
+ * underlying native resources.
+ *
+ * The static methods prefixed with `delete_` are special. They are designed to be
+ * invoked by Java through the `NativeAllocationRegistry` when the
+ * corresponding Java object becomes unreachable. These methods act as
+ * callbacks to ensure proper deallocation of native resources.
+ */
+namespace tracing_perfetto {
+/**
+ * @brief Represents extra data associated with a trace event.
+ * This class manages a collection of PerfettoTeHlExtra pointers.
+ */
+class Extra;
+
+/**
+ * @brief Emits a trace event.
+ * @param type The type of the event.
+ * @param cat The category of the event.
+ * @param name The name of the event.
+ * @param arg_ptr Pointer to Extra data.
+ */
+void trace_event(int type, const PerfettoTeCategory* cat, const char* name,
+ Extra* extra);
+
+/**
+ * @brief Gets the process track UUID.
+ */
+uint64_t get_process_track_uuid();
+
+/**
+ * @brief Gets the thread track UUID for a given PID.
+ */
+uint64_t get_thread_track_uuid(pid_t tid);
+
+/**
+ * @brief Holder for all the other classes in the file.
+ */
+class Extra {
+ public:
+ Extra();
+ void push_extra(PerfettoTeHlExtra* extra);
+ void pop_extra();
+ void clear_extras();
+ static void delete_extra(Extra* extra);
+
+ PerfettoTeHlExtra* const* get() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Extra);
+
+ // These PerfettoTeHlExtra pointers are really pointers to all the other
+ // types of extras: Category, DebugArg, Counter etc. Those objects are
+ // individually managed by Java.
+ std::vector<PerfettoTeHlExtra*> extras_;
+};
+
+/**
+ * @brief Represents a trace event category.
+ */
+class Category {
+ public:
+ Category(const std::string& name, const std::string& tag,
+ const std::string& severity);
+
+ ~Category();
+
+ void register_category();
+
+ void unregister_category();
+
+ bool is_category_enabled();
+
+ static void delete_category(Category* category);
+
+ const PerfettoTeCategory* get() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Category);
+ PerfettoTeCategory category_;
+ const std::string name_;
+ const std::string tag_;
+ const std::string severity_;
+};
+
+/**
+ * @brief Represents one end of a flow between two events.
+ */
+class Flow {
+ public:
+ Flow();
+
+ void set_process_flow(uint64_t id);
+ void set_process_terminating_flow(uint64_t id);
+ static void delete_flow(Flow* flow);
+
+ const PerfettoTeHlExtraFlow* get() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Flow);
+ PerfettoTeHlExtraFlow flow_;
+};
+
+/**
+ * @brief Represents a named track.
+ */
+class NamedTrack {
+ public:
+ NamedTrack(uint64_t id, uint64_t parent_uuid, const std::string& name);
+
+ static void delete_track(NamedTrack* track);
+
+ const PerfettoTeHlExtraNamedTrack* get() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NamedTrack);
+ const std::string name_;
+ PerfettoTeHlExtraNamedTrack track_;
+};
+
+/**
+ * @brief Represents a registered track.
+ */
+class RegisteredTrack {
+ public:
+ RegisteredTrack(uint64_t id, uint64_t parent_uuid, const std::string& name,
+ bool is_counter);
+ ~RegisteredTrack();
+
+ void register_track();
+ void unregister_track();
+ static void delete_track(RegisteredTrack* track);
+
+ const PerfettoTeHlExtraRegisteredTrack* get() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RegisteredTrack);
+ PerfettoTeRegisteredTrack registered_track_;
+ PerfettoTeHlExtraRegisteredTrack track_;
+ const std::string name_;
+ const uint64_t id_;
+ const uint64_t parent_uuid_;
+ const bool is_counter_;
+};
+
+/**
+ * @brief Represents a counter track event.
+ * @tparam T The data type of the counter (int64_t or double).
+ */
+template <typename T>
+class Counter {
+ public:
+ template <typename>
+ struct always_false : std::false_type {};
+
+ struct TypeMap {
+ using type = std::invoke_result_t<decltype([]() {
+ if constexpr (std::is_same_v<T, int64_t>) {
+ return std::type_identity<PerfettoTeHlExtraCounterInt64>{};
+ } else if constexpr (std::is_same_v<T, double>) {
+ return std::type_identity<PerfettoTeHlExtraCounterDouble>{};
+ } else {
+ return std::type_identity<void>{};
+ }
+ })>::type;
+
+ static constexpr int enum_value = []() {
+ if constexpr (std::is_same_v<T, int64_t>) {
+ return PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_INT64;
+ } else if constexpr (std::is_same_v<T, double>) {
+ return PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_DOUBLE;
+ } else {
+ static_assert(always_false<T>::value, "Unsupported type");
+ return 0; // Never reached, just to satisfy return type
+ }
+ }();
+ };
+
+ Counter() {
+ static_assert(!std::is_same_v<typename TypeMap::type, void>,
+ "Unsupported type for Counter");
+
+ typename TypeMap::type counter;
+ counter.header = {TypeMap::enum_value};
+ counter_ = std::move(counter);
+ }
+
+ void set_value(T value) {
+ if constexpr (std::is_same_v<T, int64_t>) {
+ counter_.value = value;
+ } else if constexpr (std::is_same_v<T, double>) {
+ counter_.value = value;
+ }
+ }
+
+ static void delete_counter(Counter* counter) {
+ delete counter;
+ }
+
+ const TypeMap::type* get() const {
+ return &counter_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Counter);
+ TypeMap::type counter_;
+};
+
+/**
+ * @brief Represents a debug argument for a trace event.
+ * @tparam T The data type of the argument (bool, int64_t, double, const char*).
+ */
+template <typename T>
+class DebugArg {
+ public:
+ template <typename>
+ struct always_false : std::false_type {};
+
+ struct TypeMap {
+ using type = std::invoke_result_t<decltype([]() {
+ if constexpr (std::is_same_v<T, bool>) {
+ return std::type_identity<PerfettoTeHlExtraDebugArgBool>{};
+ } else if constexpr (std::is_same_v<T, int64_t>) {
+ return std::type_identity<PerfettoTeHlExtraDebugArgInt64>{};
+ } else if constexpr (std::is_same_v<T, double>) {
+ return std::type_identity<PerfettoTeHlExtraDebugArgDouble>{};
+ } else if constexpr (std::is_same_v<T, const char*>) {
+ return std::type_identity<PerfettoTeHlExtraDebugArgString>{};
+ } else {
+ return std::type_identity<void>{};
+ }
+ })>::type;
+
+ static constexpr int enum_value = []() {
+ if constexpr (std::is_same_v<T, bool>) {
+ return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_BOOL;
+ } else if constexpr (std::is_same_v<T, int64_t>) {
+ return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_INT64;
+ } else if constexpr (std::is_same_v<T, double>) {
+ return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_DOUBLE;
+ } else if constexpr (std::is_same_v<T, const char*>) {
+ return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_STRING;
+ } else {
+ static_assert(always_false<T>::value, "Unsupported type");
+ return 0; // Never reached, just to satisfy return type
+ }
+ }();
+ };
+
+ DebugArg(const std::string& name) : name_(name) {
+ static_assert(!std::is_same_v<typename TypeMap::type, void>,
+ "Unsupported type for DebugArg");
+
+ typename TypeMap::type arg;
+ arg.header = {TypeMap::enum_value};
+ arg.name = name_.c_str();
+ arg_ = std::move(arg);
+ }
+
+ void set_value(T value) {
+ if constexpr (std::is_same_v<T, const char*>) {
+ arg_.value = value;
+ } else if constexpr (std::is_same_v<T, int64_t>) {
+ arg_.value = value;
+ } else if constexpr (std::is_same_v<T, bool>) {
+ arg_.value = value;
+ } else if constexpr (std::is_same_v<T, double>) {
+ arg_.value = value;
+ }
+ }
+
+ static void delete_arg(DebugArg* arg) {
+ delete arg;
+ }
+
+ const TypeMap::type* get() const {
+ return &arg_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DebugArg);
+ TypeMap::type arg_;
+ const std::string name_;
+};
+
+template <typename T>
+class ProtoField {
+ public:
+ template <typename>
+ struct always_false : std::false_type {};
+
+ struct TypeMap {
+ using type = std::invoke_result_t<decltype([]() {
+ if constexpr (std::is_same_v<T, int64_t>) {
+ return std::type_identity<PerfettoTeHlProtoFieldVarInt>{};
+ } else if constexpr (std::is_same_v<T, double>) {
+ return std::type_identity<PerfettoTeHlProtoFieldDouble>{};
+ } else if constexpr (std::is_same_v<T, const char*>) {
+ return std::type_identity<PerfettoTeHlProtoFieldCstr>{};
+ } else {
+ return std::type_identity<void>{};
+ }
+ })>::type;
+
+ static constexpr PerfettoTeHlProtoFieldType enum_value = []() {
+ if constexpr (std::is_same_v<T, int64_t>) {
+ return PERFETTO_TE_HL_PROTO_TYPE_VARINT;
+ } else if constexpr (std::is_same_v<T, double>) {
+ return PERFETTO_TE_HL_PROTO_TYPE_DOUBLE;
+ } else if constexpr (std::is_same_v<T, const char*>) {
+ return PERFETTO_TE_HL_PROTO_TYPE_CSTR;
+ } else {
+ static_assert(always_false<T>::value, "Unsupported type");
+ return 0; // Never reached, just to satisfy return type
+ }
+ }();
+ };
+
+ ProtoField() {
+ static_assert(!std::is_same_v<typename TypeMap::type, void>,
+ "Unsupported type for ProtoField");
+
+ typename TypeMap::type arg;
+ arg.header.type = TypeMap::enum_value;
+ arg_ = std::move(arg);
+ }
+
+ void set_value(uint32_t id, T value) {
+ if constexpr (std::is_same_v<T, int64_t>) {
+ arg_.header.id = id;
+ arg_.value = value;
+ } else if constexpr (std::is_same_v<T, double>) {
+ arg_.header.id = id;
+ arg_.value = value;
+ } else if constexpr (std::is_same_v<T, const char*>) {
+ arg_.header.id = id;
+ arg_.str = value;
+ }
+ }
+
+ static void delete_field(ProtoField* field) {
+ delete field;
+ }
+
+ const TypeMap::type* get() const {
+ return &arg_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProtoField);
+ TypeMap::type arg_;
+};
+
+class ProtoFieldNested {
+ public:
+ ProtoFieldNested();
+
+ void add_field(PerfettoTeHlProtoField* field);
+ void set_id(uint32_t id);
+ static void delete_field(ProtoFieldNested* field);
+
+ const PerfettoTeHlProtoFieldNested* get() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProtoFieldNested);
+ PerfettoTeHlProtoFieldNested field_;
+ // These PerfettoTeHlProtoField pointers are really pointers to all the other
+ // types of protos: PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldVarInt,
+ // PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldNested. Those objects are
+ // individually managed by Java.
+ std::vector<PerfettoTeHlProtoField*> fields_;
+};
+
+class Proto {
+ public:
+ Proto();
+
+ void add_field(PerfettoTeHlProtoField* field);
+ void clear_fields();
+ static void delete_proto(Proto* proto);
+
+ const PerfettoTeHlExtraProtoFields* get() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Proto);
+ PerfettoTeHlExtraProtoFields proto_;
+ // These PerfettoTeHlProtoField pointers are really pointers to all the other
+ // types of protos: PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldVarInt,
+ // PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldNested. Those objects are
+ // individually managed by Java.
+ std::vector<PerfettoTeHlProtoField*> fields_;
+};
+
+class Session {
+ public:
+ Session(bool is_backend_in_process, void* buf, size_t len);
+ ~Session();
+ Session(const Session&) = delete;
+ Session& operator=(const Session&) = delete;
+
+ bool FlushBlocking(uint32_t timeout_ms);
+ void StopBlocking();
+ std::vector<uint8_t> ReadBlocking();
+
+ static void delete_session(Session* session);
+
+ struct PerfettoTracingSessionImpl* session_ = nullptr;
+};
+
+/**
+ * @brief Activates a trigger.
+ * @param name The name of the trigger.
+ * @param ttl_ms The time-to-live of the trigger in milliseconds.
+ */
+void activate_trigger(const char* name, uint32_t ttl_ms);
+} // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tests/Android.bp b/libs/tracing_perfetto/tests/Android.bp
index d203467783..79fb704906 100644
--- a/libs/tracing_perfetto/tests/Android.bp
+++ b/libs/tracing_perfetto/tests/Android.bp
@@ -36,7 +36,6 @@ cc_test {
"android.os.flags-aconfig-cc-host",
"libbase",
"libperfetto_c",
- "liblog",
"libprotobuf-cpp-lite",
"libtracing_perfetto",
],
@@ -44,5 +43,8 @@ cc_test {
"tracing_perfetto_test.cpp",
"utils.cpp",
],
+ local_include_dirs: [
+ "include",
+ ],
test_suites: ["device-tests"],
}
diff --git a/libs/tracing_perfetto/tests/include/utils.h b/libs/tracing_perfetto/tests/include/utils.h
new file mode 100644
index 0000000000..b2630e1829
--- /dev/null
+++ b/libs/tracing_perfetto/tests/include/utils.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2024 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.
+ */
+
+// Copied from //external/perfetto/src/shared_lib/test/utils.h
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <cassert>
+#include <condition_variable>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/pb_utils.h"
+#include "perfetto/public/tracing_session.h"
+
+// Pretty printer for gtest
+void PrintTo(const PerfettoPbDecoderField& field, std::ostream*);
+
+namespace perfetto {
+namespace shlib {
+namespace test_utils {
+
+class WaitableEvent {
+ public:
+ WaitableEvent() = default;
+ void Notify() {
+ std::unique_lock<std::mutex> lock(m_);
+ notified_ = true;
+ cv_.notify_one();
+ }
+ bool WaitForNotification() {
+ std::unique_lock<std::mutex> lock(m_);
+ cv_.wait(lock, [this] { return notified_; });
+ return notified_;
+ }
+ bool IsNotified() {
+ std::unique_lock<std::mutex> lock(m_);
+ return notified_;
+ }
+
+ private:
+ std::mutex m_;
+ std::condition_variable cv_;
+ bool notified_ = false;
+};
+
+class TracingSession {
+ public:
+ class Builder {
+ public:
+ Builder() = default;
+ Builder& add_enabled_category(std::string category) {
+ enabled_categories_.push_back(std::move(category));
+ return *this;
+ }
+ Builder& add_disabled_category(std::string category) {
+ disabled_categories_.push_back(std::move(category));
+ return *this;
+ }
+ Builder& add_atrace_category(std::string category) {
+ atrace_categories_.push_back(std::move(category));
+ return *this;
+ }
+ Builder& add_atrace_category_prefer_sdk(std::string category) {
+ atrace_categories_prefer_sdk_.push_back(std::move(category));
+ return *this;
+ }
+ TracingSession Build();
+
+ private:
+ std::vector<std::string> enabled_categories_;
+ std::vector<std::string> disabled_categories_;
+ std::vector<std::string> atrace_categories_;
+ std::vector<std::string> atrace_categories_prefer_sdk_;
+ };
+
+ static TracingSession Adopt(struct PerfettoTracingSessionImpl*);
+ static TracingSession FromBytes(void *buf, size_t len);
+
+ TracingSession(TracingSession&&) noexcept;
+
+ ~TracingSession();
+
+ struct PerfettoTracingSessionImpl* session() const {
+ return session_;
+ }
+
+ bool FlushBlocking(uint32_t timeout_ms);
+ void WaitForStopped();
+ void StopBlocking();
+ std::vector<uint8_t> ReadBlocking();
+
+ private:
+ TracingSession() = default;
+ struct PerfettoTracingSessionImpl* session_;
+ std::unique_ptr<WaitableEvent> stopped_;
+};
+
+} // namespace test_utils
+} // namespace shlib
+} // namespace perfetto
+
+#endif // UTILS_H
diff --git a/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
index e9fee2e6cf..b21a090677 100644
--- a/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
+++ b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
@@ -22,6 +22,7 @@
#include <unistd.h>
#include "gtest/gtest.h"
+
#include "perfetto/public/abi/data_source_abi.h"
#include "perfetto/public/abi/heap_buffer.h"
#include "perfetto/public/abi/pb_decoder_abi.h"
diff --git a/libs/tracing_perfetto/tests/utils.cpp b/libs/tracing_perfetto/tests/utils.cpp
index 8c4d4a8925..af61bc2192 100644
--- a/libs/tracing_perfetto/tests/utils.cpp
+++ b/libs/tracing_perfetto/tests/utils.cpp
@@ -34,36 +34,17 @@
namespace perfetto {
namespace shlib {
namespace test_utils {
-namespace {
-
-std::string ToHexChars(uint8_t val) {
- std::string ret;
- uint8_t high_nibble = (val & 0xF0) >> 4;
- uint8_t low_nibble = (val & 0xF);
- static const char hex_chars[] = "0123456789ABCDEF";
- ret.push_back(hex_chars[high_nibble]);
- ret.push_back(hex_chars[low_nibble]);
- return ret;
-}
-
-} // namespace
-
TracingSession TracingSession::Builder::Build() {
perfetto::protos::TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(1024);
- auto* track_event_ds_config = trace_config.add_data_sources()->mutable_config();
- auto* ftrace_ds_config = trace_config.add_data_sources()->mutable_config();
-
- track_event_ds_config->set_name("track_event");
- track_event_ds_config->set_target_buffer(0);
-
- ftrace_ds_config->set_name("linux.ftrace");
- ftrace_ds_config->set_target_buffer(0);
-
{
- auto* ftrace_config = ftrace_ds_config->mutable_ftrace_config();
if (!atrace_categories_.empty()) {
+ auto* ftrace_ds_config = trace_config.add_data_sources()->mutable_config();
+ ftrace_ds_config->set_name("linux.ftrace");
+ ftrace_ds_config->set_target_buffer(0);
+
+ auto* ftrace_config = ftrace_ds_config->mutable_ftrace_config();
ftrace_config->add_ftrace_events("ftrace/print");
for (const std::string& cat : atrace_categories_) {
ftrace_config->add_atrace_categories(cat);
@@ -76,8 +57,14 @@ TracingSession TracingSession::Builder::Build() {
}
{
- auto* track_event_config = track_event_ds_config->mutable_track_event_config();
if (!enabled_categories_.empty() || !disabled_categories_.empty()) {
+ auto* track_event_ds_config = trace_config.add_data_sources()->mutable_config();
+
+ track_event_ds_config->set_name("track_event");
+ track_event_ds_config->set_target_buffer(0);
+
+ auto* track_event_config = track_event_ds_config->mutable_track_event_config();
+
for (const std::string& cat : enabled_categories_) {
track_event_config->add_enabled_categories(cat);
}
@@ -88,13 +75,17 @@ TracingSession TracingSession::Builder::Build() {
}
}
- struct PerfettoTracingSessionImpl* ts =
- PerfettoTracingSessionCreate(PERFETTO_BACKEND_SYSTEM);
-
std::string trace_config_string;
trace_config.SerializeToString(&trace_config_string);
- PerfettoTracingSessionSetup(ts, trace_config_string.data(), trace_config_string.length());
+ return TracingSession::FromBytes(trace_config_string.data(), trace_config_string.length());
+}
+
+TracingSession TracingSession::FromBytes(void *buf, size_t len) {
+ struct PerfettoTracingSessionImpl* ts =
+ PerfettoTracingSessionCreate(PERFETTO_BACKEND_SYSTEM);
+
+ PerfettoTracingSessionSetup(ts, buf, len);
// Fails to start here
PerfettoTracingSessionStartBlocking(ts);
@@ -177,39 +168,3 @@ std::vector<uint8_t> TracingSession::ReadBlocking() {
} // namespace test_utils
} // namespace shlib
} // namespace perfetto
-
-void PrintTo(const PerfettoPbDecoderField& field, std::ostream* pos) {
- std::ostream& os = *pos;
- PerfettoPbDecoderStatus status =
- static_cast<PerfettoPbDecoderStatus>(field.status);
- switch (status) {
- case PERFETTO_PB_DECODER_ERROR:
- os << "MALFORMED PROTOBUF";
- break;
- case PERFETTO_PB_DECODER_DONE:
- os << "DECODER DONE";
- break;
- case PERFETTO_PB_DECODER_OK:
- switch (field.wire_type) {
- case PERFETTO_PB_WIRE_TYPE_DELIMITED:
- os << "\"";
- for (size_t i = 0; i < field.value.delimited.len; i++) {
- os << perfetto::shlib::test_utils::ToHexChars(
- field.value.delimited.start[i])
- << " ";
- }
- os << "\"";
- break;
- case PERFETTO_PB_WIRE_TYPE_VARINT:
- os << "varint: " << field.value.integer64;
- break;
- case PERFETTO_PB_WIRE_TYPE_FIXED32:
- os << "fixed32: " << field.value.integer32;
- break;
- case PERFETTO_PB_WIRE_TYPE_FIXED64:
- os << "fixed64: " << field.value.integer64;
- break;
- }
- break;
- }
-}
diff --git a/libs/tracing_perfetto/tests/utils.h b/libs/tracing_perfetto/tests/utils.h
deleted file mode 100644
index 8edb4143ee..0000000000
--- a/libs/tracing_perfetto/tests/utils.h
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * Copyright 2024 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.
- */
-
-// Copied from //external/perfetto/src/shared_lib/test/utils.h
-
-#ifndef UTILS_H
-#define UTILS_H
-
-#include <cassert>
-#include <condition_variable>
-#include <cstdint>
-#include <functional>
-#include <iterator>
-#include <memory>
-#include <mutex>
-#include <ostream>
-#include <string>
-#include <vector>
-
-#include "gmock/gmock-matchers.h"
-#include "gmock/gmock-more-matchers.h"
-#include "gtest/gtest-matchers.h"
-#include "gtest/gtest.h"
-#include "perfetto/public/abi/pb_decoder_abi.h"
-#include "perfetto/public/pb_utils.h"
-#include "perfetto/public/tracing_session.h"
-
-// Pretty printer for gtest
-void PrintTo(const PerfettoPbDecoderField& field, std::ostream*);
-
-namespace perfetto {
-namespace shlib {
-namespace test_utils {
-
-class WaitableEvent {
- public:
- WaitableEvent() = default;
- void Notify() {
- std::unique_lock<std::mutex> lock(m_);
- notified_ = true;
- cv_.notify_one();
- }
- bool WaitForNotification() {
- std::unique_lock<std::mutex> lock(m_);
- cv_.wait(lock, [this] { return notified_; });
- return notified_;
- }
- bool IsNotified() {
- std::unique_lock<std::mutex> lock(m_);
- return notified_;
- }
-
- private:
- std::mutex m_;
- std::condition_variable cv_;
- bool notified_ = false;
-};
-
-class TracingSession {
- public:
- class Builder {
- public:
- Builder() = default;
- Builder& add_enabled_category(std::string category) {
- enabled_categories_.push_back(std::move(category));
- return *this;
- }
- Builder& add_disabled_category(std::string category) {
- disabled_categories_.push_back(std::move(category));
- return *this;
- }
- Builder& add_atrace_category(std::string category) {
- atrace_categories_.push_back(std::move(category));
- return *this;
- }
- Builder& add_atrace_category_prefer_sdk(std::string category) {
- atrace_categories_prefer_sdk_.push_back(std::move(category));
- return *this;
- }
- TracingSession Build();
-
- private:
- std::vector<std::string> enabled_categories_;
- std::vector<std::string> disabled_categories_;
- std::vector<std::string> atrace_categories_;
- std::vector<std::string> atrace_categories_prefer_sdk_;
- };
-
- static TracingSession Adopt(struct PerfettoTracingSessionImpl*);
-
- TracingSession(TracingSession&&) noexcept;
-
- ~TracingSession();
-
- struct PerfettoTracingSessionImpl* session() const {
- return session_;
- }
-
- bool FlushBlocking(uint32_t timeout_ms);
- void WaitForStopped();
- void StopBlocking();
- std::vector<uint8_t> ReadBlocking();
-
- private:
- TracingSession() = default;
- struct PerfettoTracingSessionImpl* session_;
- std::unique_ptr<WaitableEvent> stopped_;
-};
-
-template <typename FieldSkipper>
-class FieldViewBase {
- public:
- class Iterator {
- public:
- using iterator_category = std::input_iterator_tag;
- using value_type = const PerfettoPbDecoderField;
- using pointer = value_type;
- using reference = value_type;
- reference operator*() const {
- struct PerfettoPbDecoder decoder;
- decoder.read_ptr = read_ptr_;
- decoder.end_ptr = end_ptr_;
- struct PerfettoPbDecoderField field;
- do {
- field = PerfettoPbDecoderParseField(&decoder);
- } while (field.status == PERFETTO_PB_DECODER_OK &&
- skipper_.ShouldSkip(field));
- return field;
- }
- Iterator& operator++() {
- struct PerfettoPbDecoder decoder;
- decoder.read_ptr = read_ptr_;
- decoder.end_ptr = end_ptr_;
- PerfettoPbDecoderSkipField(&decoder);
- read_ptr_ = decoder.read_ptr;
- AdvanceToFirstInterestingField();
- return *this;
- }
- Iterator operator++(int) {
- Iterator tmp = *this;
- ++(*this);
- return tmp;
- }
-
- friend bool operator==(const Iterator& a, const Iterator& b) {
- return a.read_ptr_ == b.read_ptr_;
- }
- friend bool operator!=(const Iterator& a, const Iterator& b) {
- return a.read_ptr_ != b.read_ptr_;
- }
-
- private:
- Iterator(const uint8_t* read_ptr, const uint8_t* end_ptr,
- const FieldSkipper& skipper)
- : read_ptr_(read_ptr), end_ptr_(end_ptr), skipper_(skipper) {
- AdvanceToFirstInterestingField();
- }
- void AdvanceToFirstInterestingField() {
- struct PerfettoPbDecoder decoder;
- decoder.read_ptr = read_ptr_;
- decoder.end_ptr = end_ptr_;
- struct PerfettoPbDecoderField field;
- const uint8_t* prev_read_ptr;
- do {
- prev_read_ptr = decoder.read_ptr;
- field = PerfettoPbDecoderParseField(&decoder);
- } while (field.status == PERFETTO_PB_DECODER_OK &&
- skipper_.ShouldSkip(field));
- if (field.status == PERFETTO_PB_DECODER_OK) {
- read_ptr_ = prev_read_ptr;
- } else {
- read_ptr_ = decoder.read_ptr;
- }
- }
- friend class FieldViewBase<FieldSkipper>;
- const uint8_t* read_ptr_;
- const uint8_t* end_ptr_;
- const FieldSkipper& skipper_;
- };
- using value_type = const PerfettoPbDecoderField;
- using const_iterator = Iterator;
- template <typename... Args>
- explicit FieldViewBase(const uint8_t* begin, const uint8_t* end, Args... args)
- : begin_(begin), end_(end), s_(args...) {
- }
- template <typename... Args>
- explicit FieldViewBase(const std::vector<uint8_t>& data, Args... args)
- : FieldViewBase(data.data(), data.data() + data.size(), args...) {
- }
- template <typename... Args>
- explicit FieldViewBase(const struct PerfettoPbDecoderField& field,
- Args... args)
- : s_(args...) {
- if (field.wire_type != PERFETTO_PB_WIRE_TYPE_DELIMITED) {
- abort();
- }
- begin_ = field.value.delimited.start;
- end_ = begin_ + field.value.delimited.len;
- }
- Iterator begin() const {
- return Iterator(begin_, end_, s_);
- }
- Iterator end() const {
- return Iterator(end_, end_, s_);
- }
- PerfettoPbDecoderField front() const {
- return *begin();
- }
-
- size_t size() const {
- size_t count = 0;
- for (auto field : *this) {
- (void)field;
- count++;
- }
- return count;
- }
-
- bool ok() const {
- for (auto field : *this) {
- if (field.status != PERFETTO_PB_DECODER_OK) {
- return false;
- }
- }
- return true;
- }
-
- private:
- const uint8_t* begin_;
- const uint8_t* end_;
- FieldSkipper s_;
-};
-
-// Pretty printer for gtest
-template <typename FieldSkipper>
-void PrintTo(const FieldViewBase<FieldSkipper>& field_view, std::ostream* pos) {
- std::ostream& os = *pos;
- os << "{";
- for (PerfettoPbDecoderField f : field_view) {
- PrintTo(f, pos);
- os << ", ";
- }
- os << "}";
-}
-
-class IdFieldSkipper {
- public:
- explicit IdFieldSkipper(uint32_t id) : id_(id) {
- }
- explicit IdFieldSkipper(int32_t id) : id_(static_cast<uint32_t>(id)) {
- }
- bool ShouldSkip(const struct PerfettoPbDecoderField& field) const {
- return field.id != id_;
- }
-
- private:
- uint32_t id_;
-};
-
-class NoFieldSkipper {
- public:
- NoFieldSkipper() = default;
- bool ShouldSkip(const struct PerfettoPbDecoderField&) const {
- return false;
- }
-};
-
-// View over all the fields of a contiguous serialized protobuf message.
-//
-// Examples:
-//
-// for (struct PerfettoPbDecoderField field : FieldView(msg_begin, msg_end)) {
-// //...
-// }
-// FieldView fields2(/*PerfettoPbDecoderField*/ nested_field);
-// FieldView fields3(/*std::vector<uint8_t>*/ data);
-// size_t num = fields1.size(); // The number of fields.
-// bool ok = fields1.ok(); // Checks that the message is not malformed.
-using FieldView = FieldViewBase<NoFieldSkipper>;
-
-// Like `FieldView`, but only considers fields with a specific id.
-//
-// Examples:
-//
-// IdFieldView fields(msg_begin, msg_end, id)
-using IdFieldView = FieldViewBase<IdFieldSkipper>;
-
-// Matches a PerfettoPbDecoderField with the specified id. Accepts another
-// matcher to match the contents of the field.
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, PbField(900, VarIntField(5)));
-template <typename M>
-auto PbField(int32_t id, M m) {
- return testing::AllOf(
- testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
- testing::Field(&PerfettoPbDecoderField::id, id), m);
-}
-
-// Matches a PerfettoPbDecoderField submessage field. Accepts a container
-// matcher for the subfields.
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, MsgField(ElementsAre(...)));
-template <typename M>
-auto MsgField(M m) {
- auto f = [](const PerfettoPbDecoderField& field) { return FieldView(field); };
- return testing::AllOf(
- testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
- testing::Field(&PerfettoPbDecoderField::wire_type,
- PERFETTO_PB_WIRE_TYPE_DELIMITED),
- testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField length delimited field. Accepts a string
-// matcher.
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, StringField("string"));
-template <typename M>
-auto StringField(M m) {
- auto f = [](const PerfettoPbDecoderField& field) {
- return std::string(
- reinterpret_cast<const char*>(field.value.delimited.start),
- field.value.delimited.len);
- };
- return testing::AllOf(
- testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
- testing::Field(&PerfettoPbDecoderField::wire_type,
- PERFETTO_PB_WIRE_TYPE_DELIMITED),
- testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField VarInt field. Accepts an integer matcher
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, VarIntField(1)));
-template <typename M>
-auto VarIntField(M m) {
- auto f = [](const PerfettoPbDecoderField& field) {
- return field.value.integer64;
- };
- return testing::AllOf(
- testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
- testing::Field(&PerfettoPbDecoderField::wire_type,
- PERFETTO_PB_WIRE_TYPE_VARINT),
- testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField fixed64 field. Accepts an integer matcher
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, Fixed64Field(1)));
-template <typename M>
-auto Fixed64Field(M m) {
- auto f = [](const PerfettoPbDecoderField& field) {
- return field.value.integer64;
- };
- return testing::AllOf(
- testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
- testing::Field(&PerfettoPbDecoderField::wire_type,
- PERFETTO_PB_WIRE_TYPE_FIXED64),
- testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField fixed32 field. Accepts an integer matcher
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, Fixed32Field(1)));
-template <typename M>
-auto Fixed32Field(M m) {
- auto f = [](const PerfettoPbDecoderField& field) {
- return field.value.integer32;
- };
- return testing::AllOf(
- testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
- testing::Field(&PerfettoPbDecoderField::wire_type,
- PERFETTO_PB_WIRE_TYPE_FIXED32),
- testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField double field. Accepts a double matcher
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, DoubleField(1.0)));
-template <typename M>
-auto DoubleField(M m) {
- auto f = [](const PerfettoPbDecoderField& field) {
- return field.value.double_val;
- };
- return testing::AllOf(
- testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
- testing::Field(&PerfettoPbDecoderField::wire_type,
- PERFETTO_PB_WIRE_TYPE_FIXED64),
- testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField float field. Accepts a float matcher
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, FloatField(1.0)));
-template <typename M>
-auto FloatField(M m) {
- auto f = [](const PerfettoPbDecoderField& field) {
- return field.value.float_val;
- };
- return testing::AllOf(
- testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
- testing::Field(&PerfettoPbDecoderField::wire_type,
- PERFETTO_PB_WIRE_TYPE_FIXED32),
- testing::ResultOf(f, m));
-}
-
-// Matches a PerfettoPbDecoderField submessage field. Accepts a container
-// matcher for the subfields.
-//
-// Example:
-// PerfettoPbDecoderField field = ...
-// EXPECT_THAT(field, AllFieldsWithId(900, ElementsAre(...)));
-template <typename M>
-auto AllFieldsWithId(int32_t id, M m) {
- auto f = [id](const PerfettoPbDecoderField& field) {
- return IdFieldView(field, id);
- };
- return testing::AllOf(
- testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
- testing::Field(&PerfettoPbDecoderField::wire_type,
- PERFETTO_PB_WIRE_TYPE_DELIMITED),
- testing::ResultOf(f, m));
-}
-
-} // namespace test_utils
-} // namespace shlib
-} // namespace perfetto
-
-#endif // UTILS_H
diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp
index c35e078b02..4b7021393f 100644
--- a/libs/tracing_perfetto/tracing_perfetto.cpp
+++ b/libs/tracing_perfetto/tracing_perfetto.cpp
@@ -17,6 +17,7 @@
#include "tracing_perfetto.h"
#include <cutils/trace.h>
+
#include <cstdarg>
#include "perfetto/public/te_category_macros.h"
@@ -43,8 +44,10 @@ void traceBegin(uint64_t category, const char* name) {
void traceFormatBegin(uint64_t category, const char* fmt, ...) {
struct PerfettoTeCategory* perfettoTeCategory =
internal::toPerfettoCategory(category);
- const bool preferAtrace = internal::shouldPreferAtrace(perfettoTeCategory, category);
- const bool preferPerfetto = internal::isPerfettoCategoryEnabled(perfettoTeCategory);
+ const bool preferAtrace =
+ internal::shouldPreferAtrace(perfettoTeCategory, category);
+ const bool preferPerfetto =
+ internal::isPerfettoCategoryEnabled(perfettoTeCategory);
if (CC_LIKELY(!(preferAtrace || preferPerfetto))) {
return;
}
@@ -57,7 +60,6 @@ void traceFormatBegin(uint64_t category, const char* fmt, ...) {
vsnprintf(buf, BUFFER_SIZE, fmt, ap);
va_end(ap);
-
if (preferAtrace) {
atrace_begin(category, buf);
} else if (preferPerfetto) {
@@ -99,26 +101,28 @@ void traceAsyncEnd(uint64_t category, const char* name, int32_t cookie) {
}
void traceAsyncBeginForTrack(uint64_t category, const char* name,
- const char* trackName, int32_t cookie) {
+ const char* trackName, int32_t cookie) {
struct PerfettoTeCategory* perfettoTeCategory =
internal::toPerfettoCategory(category);
if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
atrace_async_for_track_begin(category, trackName, name, cookie);
} else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
- internal::perfettoTraceAsyncBeginForTrack(*perfettoTeCategory, name, trackName, cookie);
+ internal::perfettoTraceAsyncBeginForTrack(*perfettoTeCategory, name,
+ trackName, cookie);
}
}
void traceAsyncEndForTrack(uint64_t category, const char* trackName,
- int32_t cookie) {
+ int32_t cookie) {
struct PerfettoTeCategory* perfettoTeCategory =
internal::toPerfettoCategory(category);
if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
atrace_async_for_track_end(category, trackName, cookie);
} else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
- internal::perfettoTraceAsyncEndForTrack(*perfettoTeCategory, trackName, cookie);
+ internal::perfettoTraceAsyncEndForTrack(*perfettoTeCategory, trackName,
+ cookie);
}
}
@@ -136,8 +140,10 @@ void traceInstant(uint64_t category, const char* name) {
void traceFormatInstant(uint64_t category, const char* fmt, ...) {
struct PerfettoTeCategory* perfettoTeCategory =
internal::toPerfettoCategory(category);
- const bool preferAtrace = internal::shouldPreferAtrace(perfettoTeCategory, category);
- const bool preferPerfetto = internal::isPerfettoCategoryEnabled(perfettoTeCategory);
+ const bool preferAtrace =
+ internal::shouldPreferAtrace(perfettoTeCategory, category);
+ const bool preferPerfetto =
+ internal::isPerfettoCategoryEnabled(perfettoTeCategory);
if (CC_LIKELY(!(preferAtrace || preferPerfetto))) {
return;
}
@@ -158,7 +164,7 @@ void traceFormatInstant(uint64_t category, const char* fmt, ...) {
}
void traceInstantForTrack(uint64_t category, const char* trackName,
- const char* name) {
+ const char* name) {
struct PerfettoTeCategory* perfettoTeCategory =
internal::toPerfettoCategory(category);
@@ -181,20 +187,21 @@ void traceCounter(uint64_t category, const char* name, int64_t value) {
}
void traceCounter32(uint64_t category, const char* name, int32_t value) {
- struct PerfettoTeCategory* perfettoTeCategory = internal::toPerfettoCategory(category);
+ struct PerfettoTeCategory* perfettoTeCategory =
+ internal::toPerfettoCategory(category);
if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
atrace_int(category, name, value);
} else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
internal::perfettoTraceCounter(*perfettoTeCategory, name,
- static_cast<int64_t>(value));
+ static_cast<int64_t>(value));
}
}
bool isTagEnabled(uint64_t category) {
struct PerfettoTeCategory* perfettoTeCategory =
internal::toPerfettoCategory(category);
- return internal::isPerfettoCategoryEnabled(perfettoTeCategory)
- || atrace_is_tag_enabled(category);
+ return internal::isPerfettoCategoryEnabled(perfettoTeCategory) ||
+ atrace_is_tag_enabled(category);
}
} // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
index 9a0042aee5..447873290c 100644
--- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
@@ -14,15 +14,16 @@
* limitations under the License.
*/
+// Should match the definitions in: frameworks/native/cmds/atrace/atrace.cpp
#define FRAMEWORK_CATEGORIES(C) \
C(always, "always", "Always category") \
- C(graphics, "graphics", "Graphics category") \
+ C(graphics, "gfx", "Graphics category") \
C(input, "input", "Input category") \
C(view, "view", "View category") \
C(webview, "webview", "WebView category") \
C(windowmanager, "wm", "WindowManager category") \
C(activitymanager, "am", "ActivityManager category") \
- C(syncmanager, "syncmanager", "SyncManager category") \
+ C(syncmanager, "sm", "SyncManager category") \
C(audio, "audio", "Audio category") \
C(video, "video", "Video category") \
C(camera, "camera", "Camera category") \
@@ -33,7 +34,7 @@
C(rs, "rs", "RS category") \
C(bionic, "bionic", "Bionic category") \
C(power, "power", "Power category") \
- C(packagemanager, "packagemanager", "PackageManager category") \
+ C(packagemanager, "pm", "PackageManager category") \
C(systemserver, "ss", "System Server category") \
C(database, "database", "Database category") \
C(network, "network", "Network category") \
@@ -47,7 +48,6 @@
#include <atomic>
#include <mutex>
-#include <android_os.h>
#include <android-base/properties.h>
#include <cutils/trace.h>
#include <inttypes.h>
@@ -228,14 +228,11 @@ struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) {
}
void registerWithPerfetto(bool test) {
- if (!android::os::perfetto_sdk_tracing()) {
- return;
- }
-
static std::once_flag registration;
std::call_once(registration, [test]() {
struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
args.backends = test ? PERFETTO_BACKEND_IN_PROCESS : PERFETTO_BACKEND_SYSTEM;
+ args.shmem_size_hint_kb = 1024;
PerfettoProducerInit(args);
PerfettoTeInit();
PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES);
@@ -253,15 +250,31 @@ void perfettoTraceEnd(const struct PerfettoTeCategory& category) {
void perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
const char* trackName, uint64_t cookie) {
PERFETTO_TE(
- category, PERFETTO_TE_SLICE_BEGIN(name),
- PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid()));
+ category, PERFETTO_TE_SLICE_BEGIN(name),
+ PERFETTO_TE_PROTO_TRACK(
+ PerfettoTeNamedTrackUuid(trackName, cookie,
+ PerfettoTeProcessTrackUuid()),
+ PERFETTO_TE_PROTO_FIELD_CSTR(
+ perfetto_protos_TrackDescriptor_atrace_name_field_number,
+ trackName),
+ PERFETTO_TE_PROTO_FIELD_VARINT(
+ perfetto_protos_TrackDescriptor_parent_uuid_field_number,
+ PerfettoTeProcessTrackUuid())));
}
void perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
const char* trackName, uint64_t cookie) {
- PERFETTO_TE(
- category, PERFETTO_TE_SLICE_END(),
- PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid()));
+ PERFETTO_TE(
+ category, PERFETTO_TE_SLICE_END(),
+ PERFETTO_TE_PROTO_TRACK(
+ PerfettoTeNamedTrackUuid(trackName, cookie,
+ PerfettoTeProcessTrackUuid()),
+ PERFETTO_TE_PROTO_FIELD_CSTR(
+ perfetto_protos_TrackDescriptor_atrace_name_field_number,
+ trackName),
+ PERFETTO_TE_PROTO_FIELD_VARINT(
+ perfetto_protos_TrackDescriptor_parent_uuid_field_number,
+ PerfettoTeProcessTrackUuid())));
}
void perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
@@ -281,14 +294,35 @@ void perfettoTraceInstant(const struct PerfettoTeCategory& category, const char*
void perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
const char* trackName, const char* name) {
PERFETTO_TE(
- category, PERFETTO_TE_INSTANT(name),
- PERFETTO_TE_NAMED_TRACK(trackName, 1, PerfettoTeProcessTrackUuid()));
+ category, PERFETTO_TE_INSTANT(name),
+ PERFETTO_TE_PROTO_TRACK(
+ PerfettoTeNamedTrackUuid(trackName, 1,
+ PerfettoTeProcessTrackUuid()),
+ PERFETTO_TE_PROTO_FIELD_CSTR(
+ perfetto_protos_TrackDescriptor_atrace_name_field_number,
+ trackName),
+ PERFETTO_TE_PROTO_FIELD_VARINT(
+ perfetto_protos_TrackDescriptor_parent_uuid_field_number,
+ PerfettoTeProcessTrackUuid())));
}
void perfettoTraceCounter(const struct PerfettoTeCategory& category,
- [[maybe_unused]] const char* name, int64_t value) {
- PERFETTO_TE(category, PERFETTO_TE_COUNTER(),
- PERFETTO_TE_INT_COUNTER(value));
+ const char* name, int64_t value) {
+ PERFETTO_TE(
+ category, PERFETTO_TE_COUNTER(),
+ PERFETTO_TE_PROTO_TRACK(
+ PerfettoTeCounterTrackUuid(name,
+ PerfettoTeProcessTrackUuid()),
+ PERFETTO_TE_PROTO_FIELD_CSTR(
+ perfetto_protos_TrackDescriptor_atrace_name_field_number,
+ name),
+ PERFETTO_TE_PROTO_FIELD_VARINT(
+ perfetto_protos_TrackDescriptor_parent_uuid_field_number,
+ PerfettoTeProcessTrackUuid()),
+ PERFETTO_TE_PROTO_FIELD_BYTES(
+ perfetto_protos_TrackDescriptor_counter_field_number,
+ PERFETTO_NULL, 0)),
+ PERFETTO_TE_INT_COUNTER(value));
}
} // namespace internal
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.h b/libs/tracing_perfetto/tracing_perfetto_internal.h
index 3e1ac2a112..0e3fb07efd 100644
--- a/libs/tracing_perfetto/tracing_perfetto_internal.h
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.h
@@ -25,8 +25,6 @@ namespace tracing_perfetto {
namespace internal {
-bool isPerfettoRegistered();
-
struct PerfettoTeCategory* toPerfettoCategory(uint64_t category);
void registerWithPerfetto(bool test = false);
diff --git a/libs/tracing_perfetto/tracing_sdk.cpp b/libs/tracing_perfetto/tracing_sdk.cpp
new file mode 100644
index 0000000000..70b8be981b
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_sdk.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2024 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 "tracing_sdk.h"
+
+#include <android-base/logging.h>
+#include <cutils/trace.h>
+
+#include <cstdarg>
+#include <cstdlib>
+
+#include "perfetto/public/abi/producer_abi.h"
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#include "perfetto/public/track_event.h"
+#include "tracing_perfetto.h"
+
+namespace tracing_perfetto {
+void trace_event(int type, const PerfettoTeCategory* perfettoTeCategory,
+ const char* name, tracing_perfetto::Extra* extra) {
+ bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT(
+ perfettoTeCategory->enabled, PERFETTO_MEMORY_ORDER_RELAXED));
+ if (enabled) {
+ extra->push_extra(nullptr);
+ PerfettoTeHlEmitImpl(perfettoTeCategory->impl, type,
+ type == PERFETTO_TE_TYPE_COUNTER ? nullptr : name,
+ extra->get());
+ extra->clear_extras();
+ }
+}
+
+uint64_t get_process_track_uuid() {
+ return PerfettoTeProcessTrackUuid();
+}
+
+uint64_t get_thread_track_uuid(pid_t tid) {
+ // Cating a signed pid_t to unsigned
+ return PerfettoTeProcessTrackUuid() ^ PERFETTO_STATIC_CAST(uint64_t, tid);
+}
+
+Extra::Extra() {
+}
+
+void Extra::push_extra(PerfettoTeHlExtra* ptr) {
+ extras_.push_back(ptr);
+}
+
+void Extra::pop_extra() {
+ extras_.pop_back();
+}
+
+void Extra::clear_extras() {
+ extras_.clear();
+}
+
+void Extra::delete_extra(Extra* ptr) {
+ delete ptr;
+}
+
+PerfettoTeHlExtra* const* Extra::get() const {
+ return extras_.data();
+}
+
+Category::Category(const std::string& name, const std::string& tag,
+ const std::string& severity)
+ : category_({.enabled = &perfetto_atomic_false}),
+ name_(name),
+ tag_(tag),
+ severity_(severity) {
+}
+
+Category::~Category() {
+ unregister_category();
+}
+
+void Category::register_category() {
+ if (category_.impl) return;
+
+ std::vector<const char*> tags;
+ if (!tag_.empty()) tags.push_back(tag_.data());
+ if (!severity_.empty()) tags.push_back(severity_.data());
+
+ category_.desc = {name_.c_str(), name_.c_str(), tags.data(), tags.size()};
+
+ PerfettoTeCategoryRegister(&category_);
+ PerfettoTePublishCategories();
+}
+
+void Category::unregister_category() {
+ if (!category_.impl) return;
+
+ PerfettoTeCategoryUnregister(&category_);
+ PerfettoTePublishCategories();
+}
+
+bool Category::is_category_enabled() {
+ return PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT(
+ (category_).enabled, PERFETTO_MEMORY_ORDER_RELAXED));
+}
+
+const PerfettoTeCategory* Category::get() const {
+ return &category_;
+}
+
+void Category::delete_category(Category* ptr) {
+ delete ptr;
+}
+
+Flow::Flow() : flow_{} {
+}
+
+void Flow::set_process_flow(uint64_t id) {
+ flow_.header.type = PERFETTO_TE_HL_EXTRA_TYPE_FLOW;
+ PerfettoTeFlow ret = PerfettoTeProcessScopedFlow(id);
+ flow_.id = ret.id;
+}
+
+void Flow::set_process_terminating_flow(uint64_t id) {
+ flow_.header.type = PERFETTO_TE_HL_EXTRA_TYPE_TERMINATING_FLOW;
+ PerfettoTeFlow ret = PerfettoTeProcessScopedFlow(id);
+ flow_.id = ret.id;
+}
+
+const PerfettoTeHlExtraFlow* Flow::get() const {
+ return &flow_;
+}
+
+void Flow::delete_flow(Flow* ptr) {
+ delete ptr;
+}
+
+NamedTrack::NamedTrack(uint64_t id, uint64_t parent_uuid,
+ const std::string& name)
+ : name_(name),
+ track_{{PERFETTO_TE_HL_EXTRA_TYPE_NAMED_TRACK},
+ name_.data(),
+ id,
+ parent_uuid} {
+}
+
+const PerfettoTeHlExtraNamedTrack* NamedTrack::get() const {
+ return &track_;
+}
+
+void NamedTrack::delete_track(NamedTrack* ptr) {
+ delete ptr;
+}
+
+RegisteredTrack::RegisteredTrack(uint64_t id, uint64_t parent_uuid,
+ const std::string& name, bool is_counter)
+ : registered_track_{},
+ track_{{PERFETTO_TE_HL_EXTRA_TYPE_REGISTERED_TRACK},
+ &(registered_track_.impl)},
+ name_(name),
+ id_(id),
+ parent_uuid_(parent_uuid),
+ is_counter_(is_counter) {
+ register_track();
+}
+
+RegisteredTrack::~RegisteredTrack() {
+ unregister_track();
+}
+
+void RegisteredTrack::register_track() {
+ if (registered_track_.impl.descriptor) return;
+
+ if (is_counter_) {
+ PerfettoTeCounterTrackRegister(&registered_track_, name_.data(),
+ parent_uuid_);
+ } else {
+ PerfettoTeNamedTrackRegister(&registered_track_, name_.data(), id_,
+ parent_uuid_);
+ }
+}
+
+void RegisteredTrack::unregister_track() {
+ if (!registered_track_.impl.descriptor) return;
+ PerfettoTeRegisteredTrackUnregister(&registered_track_);
+}
+
+const PerfettoTeHlExtraRegisteredTrack* RegisteredTrack::get() const {
+ return &track_;
+}
+
+void RegisteredTrack::delete_track(RegisteredTrack* ptr) {
+ delete ptr;
+}
+
+Proto::Proto() : proto_({PERFETTO_TE_HL_EXTRA_TYPE_PROTO_FIELDS}, nullptr) {
+}
+
+void Proto::add_field(PerfettoTeHlProtoField* ptr) {
+ if (!fields_.empty()) {
+ fields_.pop_back();
+ }
+
+ fields_.push_back(ptr);
+ fields_.push_back(nullptr);
+ proto_.fields = fields_.data();
+}
+
+void Proto::clear_fields() {
+ fields_.clear();
+ proto_.fields = nullptr;
+}
+
+void Proto::delete_proto(Proto* ptr) {
+ delete ptr;
+}
+
+const PerfettoTeHlExtraProtoFields* Proto::get() const {
+ return &proto_;
+}
+
+ProtoFieldNested::ProtoFieldNested()
+ : field_({PERFETTO_TE_HL_PROTO_TYPE_NESTED}, nullptr) {
+}
+
+void ProtoFieldNested::add_field(PerfettoTeHlProtoField* ptr) {
+ if (!fields_.empty()) {
+ fields_.pop_back();
+ }
+
+ fields_.push_back(ptr);
+ fields_.push_back(nullptr);
+ field_.fields = fields_.data();
+}
+
+void ProtoFieldNested::set_id(uint32_t id) {
+ fields_.clear();
+ field_.header.id = id;
+ field_.fields = nullptr;
+}
+
+void ProtoFieldNested::delete_field(ProtoFieldNested* ptr) {
+ delete ptr;
+}
+
+const PerfettoTeHlProtoFieldNested* ProtoFieldNested::get() const {
+ return &field_;
+}
+
+Session::Session(bool is_backend_in_process, void* buf, size_t len) {
+ session_ = PerfettoTracingSessionCreate(is_backend_in_process
+ ? PERFETTO_BACKEND_IN_PROCESS
+ : PERFETTO_BACKEND_SYSTEM);
+
+ PerfettoTracingSessionSetup(session_, buf, len);
+
+ PerfettoTracingSessionStartBlocking(session_);
+}
+
+Session::~Session() {
+ PerfettoTracingSessionStopBlocking(session_);
+ PerfettoTracingSessionDestroy(session_);
+}
+
+bool Session::FlushBlocking(uint32_t timeout_ms) {
+ return PerfettoTracingSessionFlushBlocking(session_, timeout_ms);
+}
+
+void Session::StopBlocking() {
+ PerfettoTracingSessionStopBlocking(session_);
+}
+
+std::vector<uint8_t> Session::ReadBlocking() {
+ std::vector<uint8_t> data;
+ PerfettoTracingSessionReadTraceBlocking(
+ session_,
+ [](struct PerfettoTracingSessionImpl*, const void* trace_data,
+ size_t size, bool, void* user_arg) {
+ auto& dst = *static_cast<std::vector<uint8_t>*>(user_arg);
+ auto* src = static_cast<const uint8_t*>(trace_data);
+ dst.insert(dst.end(), src, src + size);
+ },
+ &data);
+ return data;
+}
+
+void Session::delete_session(Session* ptr) {
+ delete ptr;
+}
+
+void activate_trigger(const char* name, uint32_t ttl_ms) {
+ const char* names[] = {name, nullptr};
+ PerfettoProducerActivateTriggers(names, ttl_ms);
+}
+} // namespace tracing_perfetto
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 12230f99d2..87e213e394 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -136,6 +136,7 @@ cc_library_shared {
"GraphicBuffer.cpp",
"GraphicBufferAllocator.cpp",
"GraphicBufferMapper.cpp",
+ "PictureProfileHandle.cpp",
"PixelFormat.cpp",
"PublicFormat.cpp",
"StaticAsserts.cpp",
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index 8b13d78840..78e84fc4dc 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -19,10 +19,14 @@
#include <algorithm>
#include <cctype>
+#include <cstdint>
#include <numeric>
#include <optional>
#include <span>
+#include <string>
+#include <string_view>
+#include <ftl/concat.h>
#include <ftl/hash.h>
#include <log/log.h>
#include <ui/DisplayIdentification.h>
@@ -194,6 +198,21 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
const uint16_t productId =
static_cast<uint16_t>(edid[kProductIdOffset] | (edid[kProductIdOffset + 1] << 8));
+ // Bytes 12-15: display serial number, in little-endian (LSB). This field is
+ // optional and its absence is marked by having all bytes set to 0x00.
+ // Values do not represent ASCII characters.
+ constexpr size_t kSerialNumberOffset = 12;
+ if (edid.size() < kSerialNumberOffset + sizeof(uint32_t)) {
+ ALOGE("Invalid EDID: block zero S/N is truncated.");
+ return {};
+ }
+ const uint32_t blockZeroSerialNumber = edid[kSerialNumberOffset] +
+ (edid[kSerialNumberOffset + 1] << 8) + (edid[kSerialNumberOffset + 2] << 16) +
+ (edid[kSerialNumberOffset + 3] << 24);
+ const auto hashedBlockZeroSNOpt = blockZeroSerialNumber == 0
+ ? std::nullopt
+ : ftl::stable_hash(std::string_view(std::to_string(blockZeroSerialNumber)));
+
constexpr size_t kManufactureWeekOffset = 16;
if (edid.size() < kManufactureWeekOffset + sizeof(uint8_t)) {
ALOGE("Invalid EDID: manufacture week is truncated.");
@@ -212,6 +231,15 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
ALOGW_IF(manufactureOrModelYear <= 0xf,
"Invalid EDID: model year or manufacture year cannot be in the range [0x0, 0xf].");
+ constexpr size_t kMaxHorizontalPhysicalSizeOffset = 21;
+ constexpr size_t kMaxVerticalPhysicalSizeOffset = 22;
+ if (edid.size() < kMaxVerticalPhysicalSizeOffset + sizeof(uint8_t)) {
+ ALOGE("Invalid EDID: display's physical size is truncated.");
+ return {};
+ }
+ ui::Size maxPhysicalSizeInCm(edid[kMaxHorizontalPhysicalSizeOffset],
+ edid[kMaxVerticalPhysicalSizeOffset]);
+
constexpr size_t kDescriptorOffset = 54;
if (edid.size() < kDescriptorOffset) {
ALOGE("Invalid EDID: descriptors are missing.");
@@ -222,7 +250,8 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
view = view.subspan(kDescriptorOffset);
std::string_view displayName;
- std::string_view serialNumber;
+ std::string_view descriptorBlockSerialNumber;
+ std::optional<uint64_t> hashedDescriptorBlockSNOpt = std::nullopt;
std::string_view asciiText;
ui::Size preferredDTDPixelSize;
ui::Size preferredDTDPhysicalSize;
@@ -247,7 +276,10 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
asciiText = parseEdidText(descriptor);
break;
case 0xff:
- serialNumber = parseEdidText(descriptor);
+ descriptorBlockSerialNumber = parseEdidText(descriptor);
+ hashedDescriptorBlockSNOpt = descriptorBlockSerialNumber.empty()
+ ? std::nullopt
+ : ftl::stable_hash(descriptorBlockSerialNumber);
break;
}
} else if (isDetailedTimingDescriptor(view)) {
@@ -288,7 +320,7 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
if (modelString.empty()) {
ALOGW("Invalid EDID: falling back to serial number due to missing display name.");
- modelString = serialNumber;
+ modelString = descriptorBlockSerialNumber;
}
if (modelString.empty()) {
ALOGW("Invalid EDID: falling back to ASCII text due to missing serial number.");
@@ -341,11 +373,14 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
return Edid{
.manufacturerId = manufacturerId,
.productId = productId,
+ .hashedBlockZeroSerialNumberOpt = hashedBlockZeroSNOpt,
+ .hashedDescriptorBlockSerialNumberOpt = hashedDescriptorBlockSNOpt,
.pnpId = *pnpId,
.modelHash = modelHash,
.displayName = displayName,
.manufactureOrModelYear = manufactureOrModelYear,
.manufactureWeek = manufactureWeek,
+ .physicalSizeInCm = maxPhysicalSizeInCm,
.cea861Block = cea861Block,
.preferredDetailedTimingDescriptor = preferredDetailedTimingDescriptor,
};
@@ -358,10 +393,6 @@ std::optional<PnpId> getPnpId(uint16_t manufacturerId) {
return a && b && c ? std::make_optional(PnpId{a, b, c}) : std::nullopt;
}
-std::optional<PnpId> getPnpId(PhysicalDisplayId displayId) {
- return getPnpId(displayId.getManufacturerId());
-}
-
std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
uint8_t port, const DisplayIdentificationData& data) {
if (data.empty()) {
@@ -383,6 +414,7 @@ std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
return DisplayIdentificationInfo{
.id = displayId,
.name = std::string(edid->displayName),
+ .port = port,
.deviceProductInfo = buildDeviceProductInfo(*edid),
.preferredDetailedTimingDescriptor = edid->preferredDetailedTimingDescriptor,
};
@@ -392,4 +424,27 @@ PhysicalDisplayId getVirtualDisplayId(uint32_t id) {
return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
}
+PhysicalDisplayId generateEdidDisplayId(const Edid& edid) {
+ const ftl::Concat displayDetailsString{edid.manufacturerId,
+ edid.productId,
+ ftl::truncated<13>(edid.displayName),
+ edid.manufactureWeek,
+ edid.manufactureOrModelYear,
+ edid.physicalSizeInCm.getWidth(),
+ edid.physicalSizeInCm.getHeight()};
+
+ // String has to be cropped to 64 characters (at most) for ftl::stable_hash.
+ // This is fine as the accuracy or completeness of the above fields is not
+ // critical for a ID fabrication.
+ const std::optional<uint64_t> hashedDisplayDetailsOpt =
+ ftl::stable_hash(std::string_view(displayDetailsString.c_str(), 64));
+
+ // Combine the hashes via bit-shifted XORs.
+ const uint64_t id = (hashedDisplayDetailsOpt.value_or(0) << 17) ^
+ (edid.hashedBlockZeroSerialNumberOpt.value_or(0) >> 11) ^
+ (edid.hashedDescriptorBlockSerialNumberOpt.value_or(0) << 23);
+
+ return PhysicalDisplayId::fromValue(id);
+}
+
} // namespace android
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index 1ebe5973fa..a9f7cedab6 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -89,14 +89,14 @@ void GraphicBufferAllocator::dump(std::string& result, bool less) const {
uint64_t total = 0;
result.append("GraphicBufferAllocator buffers:\n");
const size_t count = list.size();
- StringAppendF(&result, "%14s | %11s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size",
+ StringAppendF(&result, "%18s | %12s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size",
"W (Stride) x H", "Layers", "Format", "Usage", "Requestor");
for (size_t i = 0; i < count; i++) {
const alloc_rec_t& rec(list.valueAt(i));
std::string sizeStr = (rec.size)
? base::StringPrintf("%7.2f KiB", static_cast<double>(rec.size) / 1024.0)
: "unknown";
- StringAppendF(&result, "%14p | %11s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n",
+ StringAppendF(&result, "%18p | %12s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n",
list.keyAt(i), sizeStr.c_str(), rec.width, rec.stride, rec.height,
rec.layerCount, rec.format, rec.usage, rec.requestorName.c_str());
total += rec.size;
diff --git a/libs/ui/PictureProfileHandle.cpp b/libs/ui/PictureProfileHandle.cpp
new file mode 100644
index 0000000000..0701e906f0
--- /dev/null
+++ b/libs/ui/PictureProfileHandle.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2009 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 <ui/PictureProfileHandle.h>
+
+#include <format>
+
+namespace android {
+
+const PictureProfileHandle PictureProfileHandle::NONE(0);
+
+::std::string toString(const PictureProfileHandle& handle) {
+ return std::format("{:#010x}", handle.getId());
+}
+
+} // namespace android
diff --git a/libs/ui/PublicFormat.cpp b/libs/ui/PublicFormat.cpp
index c9663edd7a..dbc0884bf4 100644
--- a/libs/ui/PublicFormat.cpp
+++ b/libs/ui/PublicFormat.cpp
@@ -30,6 +30,7 @@ int mapPublicFormatToHalFormat(PublicFormat f) {
case PublicFormat::DEPTH_POINT_CLOUD:
case PublicFormat::DEPTH_JPEG:
case PublicFormat::HEIC:
+ case PublicFormat::HEIC_ULTRAHDR:
case PublicFormat::JPEG_R:
return HAL_PIXEL_FORMAT_BLOB;
case PublicFormat::DEPTH16:
@@ -74,6 +75,9 @@ android_dataspace mapPublicFormatToHalDataspace(PublicFormat f) {
case PublicFormat::HEIC:
dataspace = Dataspace::HEIF;
break;
+ case PublicFormat::HEIC_ULTRAHDR:
+ dataspace = Dataspace::HEIF_ULTRAHDR;
+ break;
case PublicFormat::JPEG_R:
dataspace = Dataspace::JPEG_R;
break;
@@ -153,6 +157,9 @@ PublicFormat mapHalFormatDataspaceToPublicFormat(int format, android_dataspace d
return PublicFormat::DEPTH_JPEG;
} else if (dataSpace == static_cast<android_dataspace>(Dataspace::JPEG_R)) {
return PublicFormat::JPEG_R;
+ } else if (dataSpace == static_cast<android_dataspace>(
+ Dataspace::HEIF_ULTRAHDR)) {
+ return PublicFormat::HEIC_ULTRAHDR;
}else {
// Assume otherwise-marked blobs are also JPEG
return PublicFormat::JPEG;
diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h
index 8a14db8ffa..937e3f1486 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -20,7 +20,6 @@
#include <ostream>
#include <string>
-#include <ftl/hash.h>
#include <ftl/optional.h>
namespace android {
@@ -31,31 +30,16 @@ struct DisplayId {
// Flag indicating that the display is virtual.
static constexpr uint64_t FLAG_VIRTUAL = 1ULL << 63;
- // Flag indicating that the ID is stable across reboots.
- static constexpr uint64_t FLAG_STABLE = 1ULL << 62;
-
- // TODO(b/162612135) Remove default constructor
+ // TODO: b/162612135 - Remove default constructor.
DisplayId() = default;
constexpr DisplayId(const DisplayId&) = default;
DisplayId& operator=(const DisplayId&) = default;
+ static constexpr DisplayId fromValue(uint64_t value) { return DisplayId(value); }
constexpr bool isVirtual() const { return value & FLAG_VIRTUAL; }
- constexpr bool isStable() const { return value & FLAG_STABLE; }
uint64_t value;
- // For deserialization.
- static constexpr std::optional<DisplayId> fromValue(uint64_t);
-
- // As above, but also upcast to Id.
- template <typename Id>
- static constexpr std::optional<Id> fromValue(uint64_t value) {
- if (const auto id = Id::tryCast(DisplayId(value))) {
- return id;
- }
- return {};
- }
-
protected:
explicit constexpr DisplayId(uint64_t id) : value(id) {}
};
@@ -79,6 +63,9 @@ inline std::ostream& operator<<(std::ostream& stream, DisplayId displayId) {
// DisplayId of a physical display, such as the internal display or externally connected display.
struct PhysicalDisplayId : DisplayId {
+ // TODO: b/162612135 - Remove default constructor.
+ PhysicalDisplayId() = default;
+
static constexpr ftl::Optional<PhysicalDisplayId> tryCast(DisplayId id) {
if (id.isVirtual()) {
return std::nullopt;
@@ -86,7 +73,7 @@ struct PhysicalDisplayId : DisplayId {
return PhysicalDisplayId(id);
}
- // Returns a stable ID based on EDID information.
+ // Returns a stable ID based on EDID and port information.
static constexpr PhysicalDisplayId fromEdid(uint8_t port, uint16_t manufacturerId,
uint32_t modelHash) {
return PhysicalDisplayId(FLAG_STABLE, port, manufacturerId, modelHash);
@@ -99,13 +86,18 @@ struct PhysicalDisplayId : DisplayId {
return PhysicalDisplayId(0, port, kManufacturerId, kModelHash);
}
- // TODO(b/162612135) Remove default constructor
- PhysicalDisplayId() = default;
+ static constexpr PhysicalDisplayId fromValue(uint64_t value) {
+ return PhysicalDisplayId(value);
+ }
- constexpr uint16_t getManufacturerId() const { return static_cast<uint16_t>(value >> 40); }
constexpr uint8_t getPort() const { return static_cast<uint8_t>(value); }
private:
+ // Flag indicating that the ID is stable across reboots.
+ static constexpr uint64_t FLAG_STABLE = 1ULL << 62;
+
+ using DisplayId::DisplayId;
+
constexpr PhysicalDisplayId(uint64_t flags, uint8_t port, uint16_t manufacturerId,
uint32_t modelHash)
: DisplayId(flags | (static_cast<uint64_t>(manufacturerId) << 40) |
@@ -127,8 +119,15 @@ struct VirtualDisplayId : DisplayId {
return std::nullopt;
}
+ static constexpr VirtualDisplayId fromValue(uint64_t value) {
+ return VirtualDisplayId(SkipVirtualFlag{}, value);
+ }
+
protected:
+ struct SkipVirtualFlag {};
+ constexpr VirtualDisplayId(SkipVirtualFlag, uint64_t value) : DisplayId(value) {}
explicit constexpr VirtualDisplayId(uint64_t value) : DisplayId(FLAG_VIRTUAL | value) {}
+
explicit constexpr VirtualDisplayId(DisplayId other) : DisplayId(other) {}
};
@@ -142,20 +141,17 @@ struct HalVirtualDisplayId : VirtualDisplayId {
return std::nullopt;
}
+ static constexpr HalVirtualDisplayId fromValue(uint64_t value) {
+ return HalVirtualDisplayId(SkipVirtualFlag{}, value);
+ }
+
private:
- explicit constexpr HalVirtualDisplayId(DisplayId other) : VirtualDisplayId(other) {}
+ using VirtualDisplayId::VirtualDisplayId;
};
struct GpuVirtualDisplayId : VirtualDisplayId {
explicit constexpr GpuVirtualDisplayId(BaseId baseId) : VirtualDisplayId(FLAG_GPU | baseId) {}
- static constexpr std::optional<GpuVirtualDisplayId> fromUniqueId(const std::string& uniqueId) {
- if (const auto hashOpt = ftl::stable_hash(uniqueId)) {
- return GpuVirtualDisplayId(HashTag{}, *hashOpt);
- }
- return {};
- }
-
static constexpr std::optional<GpuVirtualDisplayId> tryCast(DisplayId id) {
if (id.isVirtual() && (id.value & FLAG_GPU)) {
return GpuVirtualDisplayId(id);
@@ -163,12 +159,12 @@ struct GpuVirtualDisplayId : VirtualDisplayId {
return std::nullopt;
}
-private:
- struct HashTag {}; // Disambiguate with BaseId constructor.
- constexpr GpuVirtualDisplayId(HashTag, uint64_t hash)
- : VirtualDisplayId(FLAG_STABLE | FLAG_GPU | hash) {}
+ static constexpr GpuVirtualDisplayId fromValue(uint64_t value) {
+ return GpuVirtualDisplayId(SkipVirtualFlag{}, value);
+ }
- explicit constexpr GpuVirtualDisplayId(DisplayId other) : VirtualDisplayId(other) {}
+private:
+ using VirtualDisplayId::VirtualDisplayId;
};
// HalDisplayId is the ID of a display which is managed by HWC.
@@ -184,20 +180,13 @@ struct HalDisplayId : DisplayId {
return HalDisplayId(id);
}
+ static constexpr HalDisplayId fromValue(uint64_t value) { return HalDisplayId(value); }
+
private:
+ using DisplayId::DisplayId;
explicit constexpr HalDisplayId(DisplayId other) : DisplayId(other) {}
};
-constexpr std::optional<DisplayId> DisplayId::fromValue(uint64_t value) {
- if (const auto id = fromValue<PhysicalDisplayId>(value)) {
- return id;
- }
- if (const auto id = fromValue<VirtualDisplayId>(value)) {
- return id;
- }
- return {};
-}
-
static_assert(sizeof(DisplayId) == sizeof(uint64_t));
static_assert(sizeof(HalDisplayId) == sizeof(uint64_t));
static_assert(sizeof(VirtualDisplayId) == sizeof(uint64_t));
diff --git a/libs/ui/include/ui/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
index 648e024d86..1e3449c004 100644
--- a/libs/ui/include/ui/DisplayIdentification.h
+++ b/libs/ui/include/ui/DisplayIdentification.h
@@ -42,6 +42,7 @@ struct DetailedTimingDescriptor {
struct DisplayIdentificationInfo {
PhysicalDisplayId id;
std::string name;
+ uint8_t port;
std::optional<DeviceProductInfo> deviceProductInfo;
std::optional<DetailedTimingDescriptor> preferredDetailedTimingDescriptor;
};
@@ -69,11 +70,15 @@ struct Cea861ExtensionBlock : ExtensionBlock {
struct Edid {
uint16_t manufacturerId;
uint16_t productId;
+ std::optional<uint64_t> hashedBlockZeroSerialNumberOpt;
+ std::optional<uint64_t> hashedDescriptorBlockSerialNumberOpt;
PnpId pnpId;
uint32_t modelHash;
+ // Up to 13 characters of ASCII text terminated by LF and padded with SP.
std::string_view displayName;
uint8_t manufactureOrModelYear;
uint8_t manufactureWeek;
+ ui::Size physicalSizeInCm;
std::optional<Cea861ExtensionBlock> cea861Block;
std::optional<DetailedTimingDescriptor> preferredDetailedTimingDescriptor;
};
@@ -81,11 +86,14 @@ struct Edid {
bool isEdid(const DisplayIdentificationData&);
std::optional<Edid> parseEdid(const DisplayIdentificationData&);
std::optional<PnpId> getPnpId(uint16_t manufacturerId);
-std::optional<PnpId> getPnpId(PhysicalDisplayId);
std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
uint8_t port, const DisplayIdentificationData&);
PhysicalDisplayId getVirtualDisplayId(uint32_t id);
+// Generates a consistent, stable, and hashed display ID that is based on the
+// display's parsed EDID fields.
+PhysicalDisplayId generateEdidDisplayId(const Edid& edid);
+
} // namespace android
diff --git a/libs/ui/include/ui/DisplayMap.h b/libs/ui/include/ui/DisplayMap.h
index 65d2b8fa31..834a3042d1 100644
--- a/libs/ui/include/ui/DisplayMap.h
+++ b/libs/ui/include/ui/DisplayMap.h
@@ -18,6 +18,7 @@
#include <ftl/small_map.h>
#include <ftl/small_vector.h>
+#include <ftl/unit.h>
namespace android::ui {
@@ -30,6 +31,8 @@ using DisplayMap = ftl::SmallMap<Key, Value, kDisplayCapacity>;
constexpr size_t kPhysicalDisplayCapacity = 3;
template <typename Key, typename Value>
using PhysicalDisplayMap = ftl::SmallMap<Key, Value, kPhysicalDisplayCapacity>;
+template <typename Key>
+using PhysicalDisplaySet = ftl::SmallMap<Key, ftl::Unit, kPhysicalDisplayCapacity>;
template <typename T>
using DisplayVector = ftl::SmallVector<T, kDisplayCapacity>;
diff --git a/libs/ui/include/ui/DynamicDisplayInfo.h b/libs/ui/include/ui/DynamicDisplayInfo.h
index 0b77754455..9d97151155 100644
--- a/libs/ui/include/ui/DynamicDisplayInfo.h
+++ b/libs/ui/include/ui/DynamicDisplayInfo.h
@@ -22,6 +22,7 @@
#include <optional>
#include <vector>
+#include <ui/FrameRateCategoryRate.h>
#include <ui/GraphicTypes.h>
#include <ui/HdrCapabilities.h>
@@ -53,6 +54,14 @@ struct DynamicDisplayInfo {
ui::DisplayModeId preferredBootDisplayMode;
std::optional<ui::DisplayMode> getActiveDisplayMode() const;
+
+ bool hasArrSupport;
+
+ // Represents frame rate for FrameRateCategory Normal and High.
+ ui::FrameRateCategoryRate frameRateCategoryRate;
+
+ // All the refresh rates supported for the default display mode.
+ std::vector<float> supportedRefreshRates;
};
} // namespace android::ui
diff --git a/libs/ui/include/ui/FloatRect.h b/libs/ui/include/ui/FloatRect.h
index 4c9c7b7ef1..4366db539f 100644
--- a/libs/ui/include/ui/FloatRect.h
+++ b/libs/ui/include/ui/FloatRect.h
@@ -51,6 +51,9 @@ public:
float bottom = 0.0f;
constexpr bool isEmpty() const { return !(left < right && top < bottom); }
+
+ // a valid rectangle has a non negative width and height
+ inline bool isValid() const { return (getWidth() >= 0) && (getHeight() >= 0); }
};
inline bool operator==(const FloatRect& a, const FloatRect& b) {
diff --git a/libs/ui/include/ui/FrameRateCategoryRate.h b/libs/ui/include/ui/FrameRateCategoryRate.h
new file mode 100644
index 0000000000..9c392d9bc8
--- /dev/null
+++ b/libs/ui/include/ui/FrameRateCategoryRate.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 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
+
+namespace android::ui {
+
+// Represents frame rate for FrameRateCategory Normal and High.
+class FrameRateCategoryRate {
+public:
+ FrameRateCategoryRate(float normal = 0, float high = 0) : mNormal(normal), mHigh(high) {}
+
+ float getNormal() const { return mNormal; }
+
+ float getHigh() const { return mHigh; }
+
+private:
+ float mNormal;
+ float mHigh;
+};
+
+} // namespace android::ui \ No newline at end of file
diff --git a/libs/ui/include/ui/PictureProfileHandle.h b/libs/ui/include/ui/PictureProfileHandle.h
new file mode 100644
index 0000000000..f8406501b4
--- /dev/null
+++ b/libs/ui/include/ui/PictureProfileHandle.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 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 <stdint.h>
+#include <array>
+#include <string>
+
+namespace android {
+
+/**
+ * An opaque value that uniquely identifies a picture profile, or a set of parameters, which
+ * describes the configuration of a picture processing pipeline that is applied to a graphic buffer
+ * to enhance its quality prior to rendering on the display.
+ */
+typedef int64_t PictureProfileId;
+
+/**
+ * A picture profile handle wraps the picture profile ID for type-safety, and represents an opaque
+ * handle that doesn't have the performance drawbacks of Binders.
+ */
+class PictureProfileHandle {
+public:
+ // A profile that represents no picture processing.
+ static const PictureProfileHandle NONE;
+
+ PictureProfileHandle() { *this = NONE; }
+ explicit PictureProfileHandle(PictureProfileId id) : mId(id) {}
+
+ PictureProfileId const& getId() const { return mId; }
+
+ inline bool operator==(const PictureProfileHandle& rhs) { return mId == rhs.mId; }
+ inline bool operator!=(const PictureProfileHandle& rhs) { return !(*this == rhs); }
+
+ // Is the picture profile effectively null, or not-specified?
+ inline bool operator!() const { return mId == NONE.mId; }
+
+ operator bool() const { return !!*this; }
+
+ friend ::std::string toString(const PictureProfileHandle& handle);
+
+private:
+ PictureProfileId mId;
+};
+
+} // namespace android
diff --git a/libs/ui/include/ui/PublicFormat.h b/libs/ui/include/ui/PublicFormat.h
index 7b7f5022d9..7c17763860 100644
--- a/libs/ui/include/ui/PublicFormat.h
+++ b/libs/ui/include/ui/PublicFormat.h
@@ -59,6 +59,7 @@ enum class PublicFormat {
DEPTH_JPEG = 0x69656963,
JPEG_R = 0x1005,
HEIC = 0x48454946,
+ HEIC_ULTRAHDR = 0x1006,
YCBCR_P210 = 0x3c,
};
diff --git a/libs/ui/include/ui/Rect.h b/libs/ui/include/ui/Rect.h
index 2eb9330cc9..2307b44eb2 100644
--- a/libs/ui/include/ui/Rect.h
+++ b/libs/ui/include/ui/Rect.h
@@ -74,12 +74,10 @@ public:
}
inline explicit Rect(const FloatRect& floatRect) {
- // Ideally we would use std::round, but we don't want to add an STL
- // dependency here, so we use an approximation
- left = static_cast<int32_t>(floatRect.left + 0.5f);
- top = static_cast<int32_t>(floatRect.top + 0.5f);
- right = static_cast<int32_t>(floatRect.right + 0.5f);
- bottom = static_cast<int32_t>(floatRect.bottom + 0.5f);
+ left = static_cast<int32_t>(std::round(floatRect.left));
+ top = static_cast<int32_t>(std::round(floatRect.top));
+ right = static_cast<int32_t>(std::round(floatRect.right));
+ bottom = static_cast<int32_t>(std::round(floatRect.bottom));
}
inline explicit Rect(const ui::Size& size) {
diff --git a/libs/ui/include_types/ui/HdrRenderTypeUtils.h b/libs/ui/include_types/ui/HdrRenderTypeUtils.h
index b0af878cdb..98018d95d1 100644
--- a/libs/ui/include_types/ui/HdrRenderTypeUtils.h
+++ b/libs/ui/include_types/ui/HdrRenderTypeUtils.h
@@ -36,7 +36,7 @@ enum class HdrRenderType {
*/
inline HdrRenderType getHdrRenderType(ui::Dataspace dataspace,
std::optional<ui::PixelFormat> pixelFormat,
- float hdrSdrRatio = 1.f) {
+ float hdrSdrRatio = 1.f, bool hasHdrMetadata = false) {
const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
const auto range = dataspace & HAL_DATASPACE_RANGE_MASK;
@@ -49,7 +49,8 @@ inline HdrRenderType getHdrRenderType(ui::Dataspace dataspace,
HAL_DATASPACE_RANGE_EXTENDED);
if ((dataspace == BT2020_LINEAR_EXT || dataspace == ui::Dataspace::V0_SCRGB) &&
- pixelFormat.has_value() && pixelFormat.value() == ui::PixelFormat::RGBA_FP16) {
+ pixelFormat.has_value() && pixelFormat.value() == ui::PixelFormat::RGBA_FP16 &&
+ hasHdrMetadata) {
return HdrRenderType::GENERIC_HDR;
}
@@ -61,4 +62,24 @@ inline HdrRenderType getHdrRenderType(ui::Dataspace dataspace,
return HdrRenderType::SDR;
}
-} // namespace android \ No newline at end of file
+/**
+ * Returns the maximum headroom allowed for this content under "idealized"
+ * display conditions (low surround luminance, high-enough display brightness).
+ *
+ * TODO: take into account hdr metadata, but square it with the fact that some
+ * HLG content has CTA.861-3 metadata
+ */
+inline float getIdealizedMaxHeadroom(ui::Dataspace dataspace) {
+ const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
+
+ switch (transfer) {
+ case HAL_DATASPACE_TRANSFER_ST2084:
+ return 10000.0f / 203.0f;
+ case HAL_DATASPACE_TRANSFER_HLG:
+ return 1000.0f / 203.0f;
+ default:
+ return 1.0f;
+ }
+}
+
+} // namespace android
diff --git a/libs/ui/tests/DisplayId_test.cpp b/libs/ui/tests/DisplayId_test.cpp
index ef686dfc83..209acba672 100644
--- a/libs/ui/tests/DisplayId_test.cpp
+++ b/libs/ui/tests/DisplayId_test.cpp
@@ -26,7 +26,6 @@ TEST(DisplayIdTest, createPhysicalIdFromEdid) {
constexpr uint32_t modelHash = 42;
const PhysicalDisplayId id = PhysicalDisplayId::fromEdid(port, manufacturerId, modelHash);
EXPECT_EQ(port, id.getPort());
- EXPECT_EQ(manufacturerId, id.getManufacturerId());
EXPECT_FALSE(VirtualDisplayId::tryCast(id));
EXPECT_FALSE(HalVirtualDisplayId::tryCast(id));
EXPECT_FALSE(GpuVirtualDisplayId::tryCast(id));
@@ -34,7 +33,7 @@ TEST(DisplayIdTest, createPhysicalIdFromEdid) {
EXPECT_TRUE(HalDisplayId::tryCast(id));
EXPECT_EQ(id, DisplayId::fromValue(id.value));
- EXPECT_EQ(id, DisplayId::fromValue<PhysicalDisplayId>(id.value));
+ EXPECT_EQ(id, PhysicalDisplayId::fromValue(id.value));
}
TEST(DisplayIdTest, createPhysicalIdFromPort) {
@@ -48,7 +47,7 @@ TEST(DisplayIdTest, createPhysicalIdFromPort) {
EXPECT_TRUE(HalDisplayId::tryCast(id));
EXPECT_EQ(id, DisplayId::fromValue(id.value));
- EXPECT_EQ(id, DisplayId::fromValue<PhysicalDisplayId>(id.value));
+ EXPECT_EQ(id, PhysicalDisplayId::fromValue(id.value));
}
TEST(DisplayIdTest, createGpuVirtualId) {
@@ -60,7 +59,7 @@ TEST(DisplayIdTest, createGpuVirtualId) {
EXPECT_FALSE(HalDisplayId::tryCast(id));
EXPECT_EQ(id, DisplayId::fromValue(id.value));
- EXPECT_EQ(id, DisplayId::fromValue<GpuVirtualDisplayId>(id.value));
+ EXPECT_EQ(id, GpuVirtualDisplayId::fromValue(id.value));
}
TEST(DisplayIdTest, createVirtualIdFromGpuVirtualId) {
@@ -75,21 +74,6 @@ TEST(DisplayIdTest, createVirtualIdFromGpuVirtualId) {
EXPECT_EQ((id.isVirtual() && isGpuVirtualId), GpuVirtualDisplayId::tryCast(id).has_value());
}
-TEST(DisplayIdTest, createGpuVirtualIdFromUniqueId) {
- static const std::string kUniqueId("virtual:ui:DisplayId_test");
- const auto idOpt = GpuVirtualDisplayId::fromUniqueId(kUniqueId);
- ASSERT_TRUE(idOpt.has_value());
- const GpuVirtualDisplayId id = idOpt.value();
- EXPECT_TRUE(VirtualDisplayId::tryCast(id));
- EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id));
- EXPECT_FALSE(HalVirtualDisplayId::tryCast(id));
- EXPECT_FALSE(PhysicalDisplayId::tryCast(id));
- EXPECT_FALSE(HalDisplayId::tryCast(id));
-
- EXPECT_EQ(id, DisplayId::fromValue(id.value));
- EXPECT_EQ(id, DisplayId::fromValue<GpuVirtualDisplayId>(id.value));
-}
-
TEST(DisplayIdTest, createHalVirtualId) {
const HalVirtualDisplayId id(42);
EXPECT_TRUE(VirtualDisplayId::tryCast(id));
@@ -99,7 +83,7 @@ TEST(DisplayIdTest, createHalVirtualId) {
EXPECT_TRUE(HalDisplayId::tryCast(id));
EXPECT_EQ(id, DisplayId::fromValue(id.value));
- EXPECT_EQ(id, DisplayId::fromValue<HalVirtualDisplayId>(id.value));
+ EXPECT_EQ(id, HalVirtualDisplayId::fromValue(id.value));
}
TEST(DisplayIdTest, createVirtualIdFromHalVirtualId) {
diff --git a/libs/ui/tests/DisplayIdentification_test.cpp b/libs/ui/tests/DisplayIdentification_test.cpp
index 76e3f66e1b..75c71a598e 100644
--- a/libs/ui/tests/DisplayIdentification_test.cpp
+++ b/libs/ui/tests/DisplayIdentification_test.cpp
@@ -33,7 +33,7 @@ namespace android {
namespace {
const unsigned char kInternalEdid[] =
- "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x00\x00\x00\x00"
+ "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x4e\x61\xbc\x00"
"\x00\x15\x01\x03\x80\x1a\x10\x78\x0a\xd3\xe5\x95\x5c\x60\x90\x27"
"\x19\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
"\x01\x01\x01\x01\x01\x01\x9e\x1b\x00\xa0\x50\x20\x12\x30\x10\x30"
@@ -54,7 +54,7 @@ const unsigned char kExternalEdid[] =
// Extended EDID with timing extension.
const unsigned char kExternalEedid[] =
- "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\x00\x00\x00\x00"
+ "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\xb1\x7f\x39\x05"
"\x29\x15\x01\x03\x80\x10\x09\x78\x0a\xee\x91\xa3\x54\x4c\x99\x26"
"\x0f\x50\x54\xbd\xef\x80\x71\x4f\x81\xc0\x81\x00\x81\x80\x95\x00"
"\xa9\xc0\xb3\x00\x01\x01\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c"
@@ -112,7 +112,7 @@ const unsigned char kHisenseTvEdid[] =
"\x07";
const unsigned char kCtlDisplayEdid[] =
- "\x00\xff\xff\xff\xff\xff\xff\x00\x0e\x8c\x9d\x24\x00\x00\x00\x00"
+ "\x00\xff\xff\xff\xff\xff\xff\x00\x0e\x8c\x9d\x24\x30\x41\xab\x00"
"\xff\x17\x01\x04\xa5\x34\x1d\x78\x3a\xa7\x25\xa4\x57\x51\xa0\x26"
"\x10\x50\x54\xbf\xef\x80\xb3\x00\xa9\x40\x95\x00\x81\x40\x81\x80"
"\x95\x0f\x71\x4f\x90\x40\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c"
@@ -191,8 +191,13 @@ TEST(DisplayIdentificationTest, parseEdid) {
EXPECT_EQ(hash("121AT11-801"), 626564263);
EXPECT_TRUE(edid->displayName.empty());
EXPECT_EQ(12610, edid->productId);
+ EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+ EXPECT_EQ(ftl::stable_hash("12345678"), edid->hashedBlockZeroSerialNumberOpt.value());
+ EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
EXPECT_EQ(21, edid->manufactureOrModelYear);
EXPECT_EQ(0, edid->manufactureWeek);
+ EXPECT_EQ(26, edid->physicalSizeInCm.width);
+ EXPECT_EQ(16, edid->physicalSizeInCm.height);
EXPECT_FALSE(edid->cea861Block);
EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
@@ -207,8 +212,14 @@ TEST(DisplayIdentificationTest, parseEdid) {
EXPECT_EQ(hash("HP ZR30w"), 918492362);
EXPECT_EQ("HP ZR30w", edid->displayName);
EXPECT_EQ(10348, edid->productId);
+ EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+ EXPECT_EQ(ftl::stable_hash("16843009"), edid->hashedBlockZeroSerialNumberOpt.value());
+ EXPECT_TRUE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
+ EXPECT_EQ(ftl::stable_hash("CN4202137Q"), edid->hashedDescriptorBlockSerialNumberOpt.value());
EXPECT_EQ(22, edid->manufactureOrModelYear);
EXPECT_EQ(2, edid->manufactureWeek);
+ EXPECT_EQ(64, edid->physicalSizeInCm.width);
+ EXPECT_EQ(40, edid->physicalSizeInCm.height);
EXPECT_FALSE(edid->cea861Block);
EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
@@ -223,8 +234,13 @@ TEST(DisplayIdentificationTest, parseEdid) {
EXPECT_EQ(hash("SAMSUNG"), 1201368132);
EXPECT_EQ("SAMSUNG", edid->displayName);
EXPECT_EQ(2302, edid->productId);
+ EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+ EXPECT_EQ(ftl::stable_hash("87654321"), edid->hashedBlockZeroSerialNumberOpt.value());
+ EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
EXPECT_EQ(21, edid->manufactureOrModelYear);
EXPECT_EQ(41, edid->manufactureWeek);
+ EXPECT_EQ(16, edid->physicalSizeInCm.width);
+ EXPECT_EQ(9, edid->physicalSizeInCm.height);
ASSERT_TRUE(edid->cea861Block);
ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock);
auto physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress;
@@ -245,8 +261,13 @@ TEST(DisplayIdentificationTest, parseEdid) {
EXPECT_EQ(hash("Panasonic-TV"), 3876373262);
EXPECT_EQ("Panasonic-TV", edid->displayName);
EXPECT_EQ(41622, edid->productId);
+ EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+ EXPECT_EQ(ftl::stable_hash("16843009"), edid->hashedBlockZeroSerialNumberOpt.value());
+ EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
EXPECT_EQ(29, edid->manufactureOrModelYear);
EXPECT_EQ(0, edid->manufactureWeek);
+ EXPECT_EQ(128, edid->physicalSizeInCm.width);
+ EXPECT_EQ(72, edid->physicalSizeInCm.height);
ASSERT_TRUE(edid->cea861Block);
ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock);
physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress;
@@ -267,8 +288,12 @@ TEST(DisplayIdentificationTest, parseEdid) {
EXPECT_EQ(hash("Hisense"), 2859844809);
EXPECT_EQ("Hisense", edid->displayName);
EXPECT_EQ(0, edid->productId);
+ EXPECT_FALSE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+ EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
EXPECT_EQ(29, edid->manufactureOrModelYear);
EXPECT_EQ(18, edid->manufactureWeek);
+ EXPECT_EQ(0, edid->physicalSizeInCm.width);
+ EXPECT_EQ(0, edid->physicalSizeInCm.height);
ASSERT_TRUE(edid->cea861Block);
ASSERT_TRUE(edid->cea861Block->hdmiVendorDataBlock);
physicalAddress = edid->cea861Block->hdmiVendorDataBlock->physicalAddress;
@@ -289,8 +314,13 @@ TEST(DisplayIdentificationTest, parseEdid) {
EXPECT_EQ(hash("LP2361"), 1523181158);
EXPECT_EQ("LP2361", edid->displayName);
EXPECT_EQ(9373, edid->productId);
+ EXPECT_TRUE(edid->hashedBlockZeroSerialNumberOpt.has_value());
+ EXPECT_EQ(ftl::stable_hash("11223344"), edid->hashedBlockZeroSerialNumberOpt.value());
+ EXPECT_FALSE(edid->hashedDescriptorBlockSerialNumberOpt.has_value());
EXPECT_EQ(23, edid->manufactureOrModelYear);
EXPECT_EQ(0xff, edid->manufactureWeek);
+ EXPECT_EQ(52, edid->physicalSizeInCm.width);
+ EXPECT_EQ(29, edid->physicalSizeInCm.height);
ASSERT_TRUE(edid->cea861Block);
EXPECT_FALSE(edid->cea861Block->hdmiVendorDataBlock);
EXPECT_EQ(1360, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
@@ -346,6 +376,22 @@ TEST(DisplayIdentificationTest, parseDisplayIdentificationData) {
EXPECT_EQ(4633127902230889474, tertiaryInfo->id.value);
}
+TEST(DisplayIdentificationTest, generateEdidDisplayId) {
+ const auto firstExternalDisplayEdidOpt = parseEdid(getExternalEdid());
+ ASSERT_TRUE(firstExternalDisplayEdidOpt);
+ const PhysicalDisplayId firstExternalDisplayId =
+ generateEdidDisplayId(firstExternalDisplayEdidOpt.value());
+
+ const auto secondExternalDisplayEdidOpt = parseEdid(getExternalEedid());
+ ASSERT_TRUE(secondExternalDisplayEdidOpt);
+ const PhysicalDisplayId secondExternalDisplayId =
+ generateEdidDisplayId(secondExternalDisplayEdidOpt.value());
+
+ // Display IDs should be unique.
+ EXPECT_EQ(4067182673952280501u, firstExternalDisplayId.value);
+ EXPECT_EQ(14712168404707886855u, secondExternalDisplayId.value);
+}
+
TEST(DisplayIdentificationTest, deviceProductInfo) {
using ManufactureYear = DeviceProductInfo::ManufactureYear;
using ManufactureWeekAndYear = DeviceProductInfo::ManufactureWeekAndYear;
@@ -432,19 +478,7 @@ TEST(DisplayIdentificationTest, deviceProductInfo) {
}
}
-TEST(DisplayIdentificationTest, fromPort) {
- // Manufacturer ID should be invalid.
- ASSERT_FALSE(getPnpId(PhysicalDisplayId::fromPort(0)));
- ASSERT_FALSE(getPnpId(PhysicalDisplayId::fromPort(0xffu)));
-}
-
-TEST(DisplayIdentificationTest, getVirtualDisplayId) {
- // Manufacturer ID should be invalid.
- ASSERT_FALSE(getPnpId(getVirtualDisplayId(0)));
- ASSERT_FALSE(getPnpId(getVirtualDisplayId(0xffff'ffffu)));
-}
-
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra" \ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/libs/ui/tests/Rect_test.cpp b/libs/ui/tests/Rect_test.cpp
index 9cc36bb15b..c3c8bd9fa4 100644
--- a/libs/ui/tests/Rect_test.cpp
+++ b/libs/ui/tests/Rect_test.cpp
@@ -99,6 +99,16 @@ TEST(RectTest, constructFromFloatRect) {
EXPECT_EQ(30, rect.right);
EXPECT_EQ(40, rect.bottom);
}
+
+ EXPECT_EQ(Rect(0, 1, -1, 0), Rect(FloatRect(0.f, 1.f, -1.f, 0.f)));
+ EXPECT_EQ(Rect(100000, 100000, -100000, -100000),
+ Rect(FloatRect(100000.f, 100000.f, -100000.f, -100000.f)));
+
+ // round down if < .5
+ EXPECT_EQ(Rect(0, 1, -1, 0), Rect(FloatRect(0.4f, 1.1f, -1.499f, 0.1f)));
+
+ // round up if >= .5
+ EXPECT_EQ(Rect(20, 20, -20, -20), Rect(FloatRect(19.5f, 19.9f, -19.5f, -19.9f)));
}
TEST(RectTest, makeInvalid) {
diff --git a/libs/vibrator/TEST_MAPPING b/libs/vibrator/TEST_MAPPING
index d782b43b57..e206761efa 100644
--- a/libs/vibrator/TEST_MAPPING
+++ b/libs/vibrator/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "libvibrator_test"
}